diff --git a/requirements.txt b/requirements.txt index fb1f8f6a..5ae2922a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,3 @@ -astc_decomp==1.0.3 -decrunch==0.4.0.post1 -fsb5==1.0 -lz4==3.1.10 -Pillow==9.0.1 -requests==2.26.0 -setuptools==58.1.0 +Pillow==9.4.0 +requests==2.28.1 +UnityPy==1.9.24 \ No newline at end of file diff --git a/unitypack/__init__.py b/unitypack/__init__.py deleted file mode 100644 index 78ea1442..00000000 --- a/unitypack/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -import pkg_resources - - -__version__ = pkg_resources.require("unitypack")[0].version - - -def load(file, env=None): - from .environment import UnityEnvironment - - if env is None: - env = UnityEnvironment() - return env.load(file) - - -def load_from_file(file, env=None): - from .environment import UnityEnvironment - - if env is None: - env = UnityEnvironment() - return env.get_asset_by_filename(file) diff --git a/unitypack/asset.py b/unitypack/asset.py deleted file mode 100644 index 137a6bce..00000000 --- a/unitypack/asset.py +++ /dev/null @@ -1,205 +0,0 @@ -import logging -import lzma -import os -from binascii import hexlify -from io import BytesIO -from uuid import UUID - -from .exceptions import ArchiveNotFound -from .object import ObjectInfo -from .type import TypeMetadata -from .utils import BinaryReader - -logger = logging.getLogger(__name__) - - -LIBRARY_UNITY_DEFAULT_RESOURCES = "library/unity default resources" - - -class Asset: - @classmethod - def from_bundle(cls, bundle, buf): - ret = cls() - ret.bundle = bundle - ret.environment = bundle.environment - offset = buf.tell() - ret._buf = BinaryReader(buf, endian=">") - - if bundle.is_unityfs: - ret._buf_ofs = buf.tell() - return ret - - if not bundle.compressed: - ret.name = buf.read_string() - header_size = buf.read_uint() - buf.read_uint() # size - else: - header_size = bundle.asset_header_size - - # FIXME: this offset needs to be explored more - ofs = buf.tell() - if bundle.compressed: - dec = lzma.LZMADecompressor() - data = dec.decompress(buf.read()) - ret._buf = BinaryReader(BytesIO(data[header_size:]), endian=">") - ret._buf_ofs = 0 - buf.seek(ofs) - else: - ret._buf_ofs = offset + header_size - 4 - if ret.is_resource: - ret._buf_ofs -= len(ret.name) - - return ret - - @classmethod - def from_file(cls, file, environment=None): - ret = cls() - ret.name = file.name - ret._buf_ofs = file.tell() - ret._buf = BinaryReader(file) - base_path = os.path.abspath(os.path.dirname(file.name)) - if environment is None: - from .environment import UnityEnvironment - ret.environment = UnityEnvironment(base_path=base_path) - return ret - - def get_asset(self, path): - try: - if ":" in path: - return self.environment.get_asset(path) - elif path == LIBRARY_UNITY_DEFAULT_RESOURCES: - logger.warning("Refusing to load " + LIBRARY_UNITY_DEFAULT_RESOURCES) - raise NotImplementedError( - LIBRARY_UNITY_DEFAULT_RESOURCES + " cannot be loaded" - ) - return self.environment.get_asset_by_filename(path) - except ArchiveNotFound as e: - logger.warning(str(e)) - return None - - def __init__(self): - self._buf_ofs = None - self._objects = {} - self.adds = [] - self.asset_refs = [self] - self.types = {} - self.typenames = {} - self.bundle = None - self.name = "" - self.long_object_ids = False - self.tree = TypeMetadata(self) - self.loaded = False - - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, self.name) - - @property - def objects(self): - if not self.loaded: - self.load() - return self._objects - - @property - def is_resource(self): - return self.name.endswith(".resource") or self.name.endswith(".resS") - - def load(self): - if self.is_resource: - self.loaded = True - return - - buf = self._buf - buf.seek(self._buf_ofs) - buf.endian = ">" - - self.metadata_size = buf.read_uint() - self.file_size = buf.read_uint() - self.format = buf.read_uint() - self.data_offset = buf.read_uint() - - if self.format >= 9: - self.endianness = buf.read_uint() - if self.endianness == 0: - buf.endian = "<" - - self.tree.load(buf) - - if 7 <= self.format <= 13: - self.long_object_ids = bool(buf.read_uint()) - - num_objects = buf.read_uint() - for i in range(num_objects): - if self.format >= 14: - buf.align() - obj = ObjectInfo(self) - obj.load(buf) - self.register_object(obj) - - if self.format >= 11: - num_adds = buf.read_uint() - for i in range(num_adds): - if self.format >= 14: - buf.align() - id = self.read_id(buf) - self.adds.append((id, buf.read_int())) - - if self.format >= 6: - num_refs = buf.read_uint() - for i in range(num_refs): - ref = AssetRef(self) - ref.load(buf) - self.asset_refs.append(ref) - - unk_string = buf.read_string() - assert not unk_string, repr(unk_string) - self.loaded = True - - def read_id(self, buf): - if self.format >= 14: - return buf.read_int64() - else: - return buf.read_int() - - def register_object(self, obj): - if obj.type_id in self.tree.type_trees: - self.types[obj.type_id] = self.tree.type_trees[obj.type_id] - elif obj.type_id not in self.types: - trees = TypeMetadata.default(self).type_trees - if obj.class_id in trees: - self.types[obj.type_id] = trees[obj.class_id] - else: - logger.warning("%r absent from structs.dat", obj.class_id) - self.types[obj.type_id] = None - - if obj.path_id in self._objects: - raise ValueError("Duplicate asset object: %r (path_id=%r)" % (obj, obj.path_id)) - - self._objects[obj.path_id] = obj - - def pretty(self): - ret = [] - for id, tree in self.tree.type_trees.items(): - ret.append("%i:" % (id)) - for child in tree.children: - ret.append("\t" + repr(child)) - return "\n".join(ret) - - -class AssetRef: - def __init__(self, source): - self.source = source - - def __repr__(self): - return "<%s (asset_path=%r, guid=%r, type=%r, file_path=%r)>" % ( - self.__class__.__name__, self.asset_path, self.guid, self.type, self.file_path - ) - - def load(self, buf): - self.asset_path = buf.read_string() - self.guid = UUID(hexlify(buf.read(16)).decode("utf-8")) - self.type = buf.read_int() - self.file_path = buf.read_string() - self.asset = None - - def resolve(self): - return self.source.get_asset(self.file_path) diff --git a/unitypack/assetbundle.py b/unitypack/assetbundle.py deleted file mode 100644 index 8c6f7a16..00000000 --- a/unitypack/assetbundle.py +++ /dev/null @@ -1,257 +0,0 @@ -import lzma -import struct -from io import BytesIO - -from .asset import Asset -from .enums import CompressionType -from .utils import BinaryReader, lz4_decompress - - -SIGNATURE_RAW = "UnityRaw" -SIGNATURE_WEB = "UnityWeb" -SIGNATURE_FS = "UnityFS" - - -class AssetBundle: - def __init__(self, environment): - self.environment = environment - self.assets = [] - - def __repr__(self): - if hasattr(self, "name"): - return "<%s %r>" % (self.__class__.__name__, self.name) - return "<%s>" % (self.__class__.__name__) - - @property - def is_unityfs(self): - return self.signature == SIGNATURE_FS - - @property - def compressed(self): - return self.signature == SIGNATURE_WEB - - def load(self, file): - buf = BinaryReader(file, endian=">") - self.path = file.name - - # Verify that the format starts with b"Unity" - position = buf.tell() - if buf.read(5) != b"Unity": - raise NotImplementedError("File does not start with b'Unity': %r" % self.path) - buf.seek(position) - - self.signature = buf.read_string() - self.format_version = buf.read_int() - self.unity_version = buf.read_string() - self.generator_version = buf.read_string() - - if self.is_unityfs: - self.load_unityfs(buf) - elif self.signature in (SIGNATURE_RAW, SIGNATURE_WEB): - self.load_raw(buf) - else: - raise NotImplementedError("Unrecognized file signature %r in %r" % (self.signature, self.path)) - - def load_raw(self, buf): - self.file_size = buf.read_uint() - self.header_size = buf.read_int() - - self.file_count = buf.read_int() - self.bundle_count = buf.read_int() - - if self.format_version >= 2: - self.bundle_size = buf.read_uint() # without header_size - - if self.format_version >= 3: - self.uncompressed_bundle_size = buf.read_uint() # without header_size - - if self.header_size >= 60: - self.compressed_file_size = buf.read_uint() # with header_size - self.asset_header_size = buf.read_uint() - - buf.read_int() - buf.read_byte() - self.name = buf.read_string() - - # Preload assets - buf.seek(self.header_size) - if not self.compressed: - num_assets = buf.read_int() - else: - num_assets = 1 - for i in range(num_assets): - asset = Asset.from_bundle(self, buf) - self.assets.append(asset) - - def read_compressed_data(self, buf, compression): - data = buf.read(self.ciblock_size) - if compression == CompressionType.NONE: - return data - - if compression in (CompressionType.LZ4, CompressionType.LZ4HC): - return lz4_decompress(data, self.uiblock_size) - - raise NotImplementedError("Unimplemented compression method: %r" % (compression)) - - def load_unityfs(self, buf): - self.file_size = buf.read_int64() - self.ciblock_size = buf.read_uint() - self.uiblock_size = buf.read_uint() - flags = buf.read_uint() - if self.format_version >= 7: - buf.seek((buf.tell() + 15) // 16 * 16) - compression = CompressionType(flags & 0x3F) - eof_metadata = flags & 0x80 - if eof_metadata: - orig_pos = buf.tell() - buf.seek(-self.ciblock_size, 2) - data = self.read_compressed_data(buf, compression) - if eof_metadata: - buf.seek(orig_pos) - - blk = BinaryReader(BytesIO(data), endian=">") - self.guid = blk.read(16) - num_blocks = blk.read_int() - blocks = [] - for i in range(num_blocks): - busize, bcsize = blk.read_int(), blk.read_int() - bflags = blk.read_int16() - blocks.append(ArchiveBlockInfo(busize, bcsize, bflags)) - - num_nodes = blk.read_int() - nodes = [] - for i in range(num_nodes): - ofs = blk.read_int64() - size = blk.read_int64() - status = blk.read_int() - name = blk.read_string() - nodes.append((ofs, size, status, name)) - - storage = ArchiveBlockStorage(blocks, buf) - for ofs, size, status, name in nodes: - storage.seek(ofs) - asset = Asset.from_bundle(self, storage) - asset.name = name - self.assets.append(asset) - - # Hacky - self.name = self.assets[0].name - - -class ArchiveBlockInfo: - def __init__(self, usize, csize, flags): - self.uncompressed_size = usize - self.compressed_size = csize - self.flags = flags - - def __repr__(self): - return "<%s: %d %d %r %r>" % ( - self.__class__.__name__, - self.uncompressed_size, self.compressed_size, - self.compressed, self.compression_type - ) - - @property - def compressed(self): - return self.compression_type != CompressionType.NONE - - @property - def compression_type(self): - return CompressionType(self.flags & 0x3f) - - def decompress(self, buf): - if not self.compressed: - return buf - ty = self.compression_type - if ty == CompressionType.LZMA: - props, dict_size = struct.unpack(" 0: - if len(part) == 0: - raise EOFError() - size -= len(part) - self.cursor += len(part) - buf += part - return bytes(buf) - - def seek(self, offset, whence=0): - new_cursor = 0 - if whence == 1: - new_cursor = offset + self.cursor - elif whence == 2: - new_cursor = self.maxpos + offset - else: - new_cursor = offset - if self.cursor != new_cursor: - self._seek(new_cursor) - - def tell(self): - return self.cursor - - def _seek(self, new_cursor): - self.cursor = new_cursor - if not self.in_current_block(new_cursor): - self.seek_to_block(new_cursor) - self.current_stream.seek(new_cursor - self.current_block_start) - - def in_current_block(self, pos): - if self.current_block is None: - return False - end = self.current_block_start + self.current_block.uncompressed_size - return self.current_block_start <= pos and pos < end - - def seek_to_block(self, pos): - baseofs = 0 - ofs = 0 - for b in self.blocks: - if ofs + b.uncompressed_size > pos: - self.current_block = b - break - baseofs += b.compressed_size - ofs += b.uncompressed_size - else: - self.current_block = None - self.current_stream = BytesIO(b"") - return - - self.current_block_start = ofs - self.stream.seek(self.basepos + baseofs) - buf = BytesIO(self.stream.read(self.current_block.compressed_size)) - self.current_stream = self.current_block.decompress(buf) diff --git a/unitypack/classes.json b/unitypack/classes.json deleted file mode 100644 index 520df20c..00000000 --- a/unitypack/classes.json +++ /dev/null @@ -1 +0,0 @@ -{"1": "GameObject", "2": "Component", "3": "LevelGameManager", "4": "Transform", "5": "TimeManager", "6": "GlobalGameManager", "7": "GameManager", "8": "Behaviour", "9": "GameManager", "1034": "NativeFormatImporter", "11": "AudioManager", "12": "ParticleAnimator", "13": "InputManager", "1038": "LibraryAssetImporter", "15": "EllipsoidParticleEmitter", "1040": "ModelImporter", "17": "Pipeline", "18": "EditorExtension", "19": "Physics2DSettings", "20": "Camera", "21": "Material", "1046": "DDSImporter", "23": "MeshRenderer", "1048": "InspectorExpandedState", "25": "Renderer", "26": "ParticleRenderer", "27": "Texture", "28": "Texture2D", "29": "SceneSettings", "30": "GraphicsSettings", "31": "PipelineManager", "33": "MeshFilter", "35": "GameManager", "1030": "DefaultImporter", "41": "OcclusionPortal", "43": "Mesh", "45": "Skybox", "46": "GameManager", "47": "QualitySettings", "48": "Shader", "49": "TextAsset", "50": "Rigidbody2D", "51": "Physics2DManager", "52": "NotificationManager", "53": "Collider2D", "54": "Rigidbody", "55": "PhysicsManager", "56": "Collider", "57": "Joint", "58": "CircleCollider2D", "59": "HingeJoint", "60": "PolygonCollider2D", "61": "BoxCollider2D", "62": "PhysicsMaterial2D", "63": "GameManager", "64": "MeshCollider", "65": "BoxCollider", "66": "SpriteCollider2D", "1035": "MonoImporter", "68": "EdgeCollider2D", "71": "AnimationManager", "72": "ComputeShader", "74": "AnimationClip", "75": "ConstantForce", "76": "WorldParticleCollider", "1101": "AnimatorStateTransition", "78": "TagManager", "1037": "AssetServerCache", "81": "AudioListener", "82": "AudioSource", "83": "AudioClip", "84": "RenderTexture", "1109": "AnimatorTransition", "1110": "SpeedTreeImporter", "87": "MeshParticleEmitter", "88": "ParticleEmitter", "89": "Cubemap", "90": "Avatar", "91": "AnimatorController", "92": "GUILayer", "93": "RuntimeAnimatorController", "94": "ScriptMapper", "95": "Animator", "96": "TrailRenderer", "98": "DelayedCallManager", "102": "TextMesh", "1041": "FBXImporter", "104": "RenderSettings", "108": "Light", "109": "CGProgram", "110": "BaseAnimationTrack", "111": "Animation", "114": "MonoBehaviour", "115": "MonoScript", "116": "MonoManager", "117": "Texture3D", "118": "NewAnimationTrack", "119": "Projector", "120": "LineRenderer", "121": "Flare", "122": "Halo", "123": "LensFlare", "124": "FlareLayer", "125": "HaloLayer", "126": "NavMeshAreas", "127": "HaloManager", "128": "Font", "129": "PlayerSettings", "130": "NamedObject", "131": "GUITexture", "132": "GUIText", "133": "GUIElement", "134": "PhysicMaterial", "135": "SphereCollider", "136": "CapsuleCollider", "137": "SkinnedMeshRenderer", "138": "FixedJoint", "1027": "GUIDSerializer", "140": "RaycastCollider", "141": "BuildSettings", "142": "AssetBundle", "143": "CharacterController", "144": "CharacterJoint", "145": "SpringJoint", "146": "WheelCollider", "147": "ResourceManager", "148": "NetworkView", "149": "NetworkManager", "150": "PreloadData", "1049": "AnnotationManager", "152": "MovieTexture", "153": "ConfigurableJoint", "154": "TerrainCollider", "155": "MasterServerInterface", "156": "TerrainData", "157": "LightmapSettings", "158": "WebCamTexture", "159": "EditorSettings", "160": "InteractiveCloth", "161": "ClothRenderer", "162": "EditorUserSettings", "163": "SkinnedCloth", "164": "AudioReverbFilter", "165": "AudioHighPassFilter", "166": "AudioChorusFilter", "167": "AudioReverbZone", "168": "AudioEchoFilter", "169": "AudioLowPassFilter", "170": "AudioDistortionFilter", "171": "SparseTexture", "1053": "ASTCImporter", "1113": "LightmapParameters", "180": "AudioBehaviour", "181": "AudioFilter", "182": "WindZone", "183": "Cloth", "184": "SubstanceArchive", "185": "ProceduralMaterial", "186": "ProceduralTexture", "1029": "DefaultAsset", "191": "OffMeshLink", "192": "OcclusionArea", "193": "Tree", "194": "NavMeshObsolete", "195": "NavMeshAgent", "196": "NavMeshSettings", "197": "LightProbesLegacy", "198": "ParticleSystem", "199": "ParticleSystemRenderer", "200": "ShaderVariantCollection", "205": "LODGroup", "206": "BlendTree", "207": "Motion", "208": "NavMeshObstacle", "210": "TerrainInstance", "212": "SpriteRenderer", "213": "Sprite", "214": "CachedSpriteAtlas", "215": "ReflectionProbe", "216": "ReflectionProbes", "1031": "TextScriptImporter", "220": "LightProbeGroup", "221": "AnimatorOverrideController", "222": "CanvasRenderer", "223": "Canvas", "224": "RectTransform", "225": "CanvasGroup", "226": "BillboardAsset", "227": "BillboardRenderer", "228": "SpeedTreeWindAsset", "229": "AnchoredJoint2D", "230": "Joint2D", "231": "SpringJoint2D", "232": "DistanceJoint2D", "233": "HingeJoint2D", "234": "SliderJoint2D", "235": "WheelJoint2D", "238": "NavMeshData", "240": "AudioMixer", "241": "AudioMixerController", "243": "AudioMixerGroupController", "244": "AudioMixerEffectController", "245": "AudioMixerSnapshotController", "246": "PhysicsUpdateBehaviour2D", "247": "ConstantForce2D", "248": "Effector2D", "249": "AreaEffector2D", "250": "PointEffector2D", "251": "PlatformEffector2D", "252": "SurfaceEffector2D", "258": "LightProbes", "1045": "EditorBuildSettings", "271": "SampleClip", "272": "AudioMixerSnapshot", "273": "AudioMixerGroup", "1032": "SceneAsset", "1028": "AssetMetaData", "290": "AssetBundleManifest", "300": "RuntimeInitializeOnLoadManager", "1050": "PluginImporter", "1042": "TrueTypeFontImporter", "1051": "EditorUserBuildSettings", "1120": "LightmapSnapshot", "1052": "PVRImporter", "1026": "HierarchyState", "1054": "KTXImporter", "1044": "MovieImporter", "1112": "SubstanceImporter", "1111": "AnimatorTransitionBase", "1102": "AnimatorState", "1107": "AnimatorStateMachine", "1105": "HumanTemplate", "1001": "Prefab", "1002": "EditorExtensionImpl", "1003": "AssetImporter", "1004": "AssetDatabase", "1005": "Mesh3DSImporter", "1006": "TextureImporter", "1007": "ShaderImporter", "1008": "ComputeShaderImporter", "1011": "AvatarMask", "1108": "PreviewAssetType", "1020": "AudioImporter"} diff --git a/unitypack/engine/__init__.py b/unitypack/engine/__init__.py deleted file mode 100644 index e2f73cb0..00000000 --- a/unitypack/engine/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -from .animation import ( - Animation, AnimationClip, Animator, AnimatorController, Motion, - ParticleAnimator, RuntimeAnimatorController -) -from .audio import AudioClip, AudioSource, StreamedResource -from .component import Behaviour, Component, Transform -from .font import Font -from .mesh import Mesh, SubMesh, VertexData, MeshFilter -from .movie import MovieTexture -from .object import GameObject -from .particle import EllipsoidParticleEmitter, MeshParticleEmitter, ParticleEmitter, ParticleSystem -from .physics import BoxCollider, BoxCollider2D, Collider, Collider2D, Rigidbody2D -from .renderer import MeshRenderer, ParticleRenderer, ParticleSystemRenderer, Renderer -from .text import TextAsset, TextMesh, Shader -from .texture import Material, Sprite, Texture2D, StreamingInfo diff --git a/unitypack/engine/animation.py b/unitypack/engine/animation.py deleted file mode 100644 index d9607c18..00000000 --- a/unitypack/engine/animation.py +++ /dev/null @@ -1,84 +0,0 @@ -from enum import IntEnum - -from .component import Behaviour, Component -from .object import Object, field - - -class Animation(Behaviour): - animate_physics = field("m_AnimatePhysics", bool) - culling_type = field("m_CullingType") - play_automatically = field("m_PlayAutomatically", bool) - wrap_mode = field("m_WrapMode") - animation = field("m_Animation") - animations = field("m_Animations") - - -class Motion(Object): - pass - - -class AnimationClip(Motion): - pass - - -class RuntimeAnimatorController(Object): - animation_clips = field("m_AnimationClips") - - -class AnimatorController(RuntimeAnimatorController): - controller = field("m_Controller") - controller_size = field("m_ControllerSize") - multithreaded_state_machine = field("m_MultiThreadedStateMachine") - state_machine_behaviours = field("m_StateMachineBehaviours") - state_machine_behaviour_vector_description = field("m_StateMachineBehaviourVectorDescription") - TOS = field("m_TOS") - - -class AnimatorCullingMode(IntEnum): - AlwaysAnimate = 0 - CullUpdateTransforms = 1 - CullCompletely = 2 - BasedOnRenderers = 1 - - -class AnimatorUpdateMode(IntEnum): - Normal = 0 - AnimatePhysics = 1 - unscaledTime = 2 - - -class Animator(Behaviour): - allow_constant_clip_sampling_optimization = field("m_AllowConstantClipSamplingOptimization", bool) - apply_root_motion = field("m_ApplyRootMotion", bool) - avatar = field("m_Avatar") - controller = field("m_Controller") - culling_mode = field("m_CullingMode", AnimatorCullingMode) - has_transform_hierarchy = field("m_HasTransformHierarchy", bool) - linear_velocity_binding = field("m_LinearVelocityBlending", bool) - update_mode = field("m_UpdateMode", AnimatorUpdateMode) - - -class ParticleAnimator(Component): - autodestruct = field("autodestruct", bool) - damping = field("damping") - does_animate_color = field("Does Animate Color?", bool) - force = field("force") - local_rotation_axis = field("localRotationAxis") - rnd_force = field("rndForce") - stop_simulation = field("stopSimulation") - size_grow = field("sizeGrow") - world_rotation_axis = field("worldRotationAxis") - - @property - def color_animation(self): - ret = [] - i = 0 - while True: - k = "colorAnimation[%i]" % (i) - if k in self._obj: - ret.append(self._obj[k]) - else: - break - i += 1 - - return ret diff --git a/unitypack/engine/audio.py b/unitypack/engine/audio.py deleted file mode 100644 index f92be824..00000000 --- a/unitypack/engine/audio.py +++ /dev/null @@ -1,85 +0,0 @@ -import logging -from enum import IntEnum - -from .component import Behaviour -from .object import Object, field - - -class AudioFormat(IntEnum): - UNKNOWN = 0 - ACC = 1 - AIFF = 2 - IT = 10 - MOD = 12 - MPEG = 13 - OGGVORBIS = 14 - S3M = 17 - WAV = 20 - XM = 21 - XMA = 22 - VAG = 23 - AUDIOQUEUE = 24 - - -class AudioRolloffMode(IntEnum): - Logarithmic = 0 - Linear = 1 - Custom = 2 - - -class AudioClip(Object): - bits_per_sample = field("m_BitsPerSample") - channels = field("m_Channels") - compression_format = field("m_CompressionFormat") - frequency = field("m_Frequency") - is_tracker_format = field("m_IsTrackerFormat") - legacy3d = field("m_Legacy3D") - length = field("m_Length") - load_in_background = field("m_LoadInBackground") - load_type = field("m_LoadType") - preload_audio_data = field("m_PreloadAudioData") - subsound_index = field("m_SubsoundIndex") - resource = field("m_Resource") - - @property - def data(self): - if not hasattr(self, "_data"): - self._data = self.resource.get_data() - return self._data - - -class AudioSource(Behaviour): - bypass_effects = field("BypassEffects", bool) - bypass_listener_effects = field("BypassListenerEffects", bool) - bypass_reverb_zones = field("BypassReverbZones", bool) - clip = field("m_audioClip") - doppler_level = field("DopplerLevel") - loop = field("Loop", bool) - max_distance = field("MaxDistance") - min_distance = field("MinDistance") - mute = field("Mute", bool) - output_audio_mixer_group = field("OutputAudioMixerGroup") - pan_stereo = field("Pan2D") - pitch = field("m_Pitch") - play_on_awake = field("m_PlayOnAwake", bool) - priority = field("Priority") - rolloff_mode = field("rolloffMode", AudioRolloffMode) - volume = field("m_Volume") - - rolloff_custom_curve = field("rolloffCustomCurve") - reverb_zone_mix_custom_curve = field("reverbZoneMixCustomCurve") - pan_level_custom_curve = field("panLevelCustomCurve") - spread_custom_curve = field("spreadCustomCurve") - - -class StreamedResource(Object): - offset = field("m_Offset") - source = field("m_Source") - size = field("m_Size") - - def get_data(self): - if not self.asset: - logging.warning("No data available for StreamedResource") - return b"" - self.asset._buf.seek(self.asset._buf_ofs + self.offset) - return self.asset._buf.read(self.size) diff --git a/unitypack/engine/component.py b/unitypack/engine/component.py deleted file mode 100644 index cd14f01f..00000000 --- a/unitypack/engine/component.py +++ /dev/null @@ -1,17 +0,0 @@ -from .object import Object, field - - -class Component(Object): - game_object = field("m_GameObject") - - -class Behaviour(Component): - enabled = field("m_Enabled", bool) - - -class Transform(Component): - position = field("m_LocalPosition") - rotation = field("m_LocalRotation") - scale = field("m_LocalScale") - parent = field("m_Father") - children = field("m_Children") diff --git a/unitypack/engine/font.py b/unitypack/engine/font.py deleted file mode 100644 index 52c5256d..00000000 --- a/unitypack/engine/font.py +++ /dev/null @@ -1,12 +0,0 @@ -from .object import Object, field - - -class Font(Object): - data = field("m_FontData") - ascent = field("m_Ascent", float) - character_padding = field("m_CharacterPadding") - character_spacing = field("m_CharacterSpacing") - font_size = field("m_FontSize", float) - kerning = field("m_Kerning", float) - line_spacing = field("m_LineSpacing", float) - pixel_scale = field("m_PixelScale", float) diff --git a/unitypack/engine/mesh.py b/unitypack/engine/mesh.py deleted file mode 100644 index 6f43aefe..00000000 --- a/unitypack/engine/mesh.py +++ /dev/null @@ -1,42 +0,0 @@ -from .component import Component -from .object import Object, field - - -class Mesh(Object): - usage_flags = field("m_MeshUsageFlags") - keep_indices = field("m_KeepIndices") - baked_convex_collision_mesh = field("m_BakedConvexCollisionMesh") - baked_triangle_collision_mesh = field("m_BakedTriangleCollisionMesh") - compressed_mesh = field("m_CompressedMesh") - is_readable = field("m_IsReadable") - localAABB = field("m_LocalAABB") - root_bone_name_hash = field("m_RootBoneNameHash") - mesh_compression = field("m_MeshCompression") - bone_name_hashes = field("m_BoneNameHashes") - bind_pose = field("m_BindPose") - shapes = field("m_Shapes") - skin = field("m_Skin") - submeshes = field("m_SubMeshes") - keep_vertices = field("m_KeepVertices") - index_buffer = field("m_IndexBuffer") - vertex_data = field("m_VertexData") - - -class SubMesh(Object): - first_byte = field("firstByte") - first_vertex = field("firstVertex") - index_count = field("indexCount") - localAABB = field("localAABB") - topology = field("topology") - vertex_count = field("vertexCount") - - -class VertexData(Object): - channels = field("m_Channels") - current_channels = field("m_CurrentChannels") - data = field("m_DataSize") - vertex_count = field("m_VertexCount") - - -class MeshFilter(Component): - pass diff --git a/unitypack/engine/movie.py b/unitypack/engine/movie.py deleted file mode 100644 index a8aec1a3..00000000 --- a/unitypack/engine/movie.py +++ /dev/null @@ -1,9 +0,0 @@ -from .object import field -from .texture import Texture - - -class MovieTexture(Texture): - audio_clip = field("m_AudioClip") - color_space = field("m_ColorSpace") - loop = field("m_Loop", bool) - movie_data = field("m_MovieData") diff --git a/unitypack/engine/object.py b/unitypack/engine/object.py deleted file mode 100644 index 45b8fc84..00000000 --- a/unitypack/engine/object.py +++ /dev/null @@ -1,32 +0,0 @@ -def field(f, cast=None, **kwargs): - def _inner(self): - if "default" in kwargs: - ret = self._obj.get(f, kwargs["default"]) - else: - ret = self._obj[f] - if cast: - ret = cast(ret) - return ret - return property(_inner) - - -class Object: - def __init__(self, data=None): - if data is None: - data = {} - self._obj = data - - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, self.name) - - def __str__(self): - return self.name - - name = field("m_Name", default="") - - -class GameObject(Object): - active = field("m_IsActive") - component = field("m_Component") - layer = field("m_Layer") - tag = field("m_Tag") diff --git a/unitypack/engine/particle.py b/unitypack/engine/particle.py deleted file mode 100644 index 69083c7d..00000000 --- a/unitypack/engine/particle.py +++ /dev/null @@ -1,39 +0,0 @@ -from .component import Component -from .object import field - - -class ParticleEmitter(Component): - angular_velocity = field("angularVelocity") - emit = field("m_Emit", bool) - emitter_velocity_scale = field("emitterVelocityScale") - max_emission = field("maxEmission") - max_energy = field("maxEnergy") - max_size = field("maxSize") - min_emission = field("minEmission") - min_energy = field("minEnergy") - min_size = field("minSize") - rnd_angular_velocity = field("rndAngularVelocity") - rnd_rotation = field("rndRotation") - rnd_velocity = field("rndVelocity") - use_worldspace = field("Simulate in Worldspace?", bool) - world_velocity = field("worldVelocity") - - local_velocity = field("localVelocity") - one_shot = field("m_OneShot", bool) - tangent_velocity = field("tangentVelocity") - - -class EllipsoidParticleEmitter(ParticleEmitter): - min_emitter_range = field("m_MinEmitterRange") - - -class MeshParticleEmitter(ParticleEmitter): - mesh = field("m_Mesh") - interpolate_triangles = field("m_InterpolateTriangles", bool) - max_normal_velocity = field("m_MaxNormalVelocity") - min_normal_velocity = field("m_MinNormalVelocity") - systematic = field("m_Systematic", bool) - - -class ParticleSystem(Component): - pass diff --git a/unitypack/engine/physics.py b/unitypack/engine/physics.py deleted file mode 100644 index bb3511d3..00000000 --- a/unitypack/engine/physics.py +++ /dev/null @@ -1,43 +0,0 @@ -from enum import IntEnum - -from .component import Behaviour, Component -from .object import field - - -class Collider(Component): - material = field("m_Material") - is_trigger = field("m_IsTrigger", bool) - - -class BoxCollider(Collider): - center = field("m_Center") - size = field("m_Size") - - -class Collider2D(Behaviour): - is_trigger = field("m_IsTrigger") - material = field("m_Material") - offset = field("m_Offset") - used_by_effector = field("m_UsedByEffector", bool) - - -class BoxCollider2D(Collider2D): - size = field("m_Size") - - -class RigidbodySleepMode2D(IntEnum): - NeverSleep = 0 - StartAwake = 1 - StartAsleep = 2 - - -class Rigidbody2D(Component): - angular_drag = field("m_AngularDrag") - collision_detection = field("m_CollisionDetection") - constraints = field("m_Constraints") - drag = field("m_LinearDrag") - gravity_scale = field("m_GravityScale") - interpolate = field("m_Interpolate") - is_kinematic = field("m_IsKinematic", bool) - mass = field("m_Mass") - sleep_mode = field("m_SleepingMode", RigidbodySleepMode2D) diff --git a/unitypack/engine/renderer.py b/unitypack/engine/renderer.py deleted file mode 100644 index d5c836f6..00000000 --- a/unitypack/engine/renderer.py +++ /dev/null @@ -1,83 +0,0 @@ -from enum import IntEnum - -from .component import Component -from .object import field - - -class ReflectionProbeUsage(IntEnum): - Off = 0 - BlendProbes = 1 - BlendProbesAndSkybox = 2 - Simple = 3 - - -class ShadowCastingMode(IntEnum): - Off = 0 - On = 1 - TwoSided = 2 - ShadowsOnly = 3 - - -class Renderer(Component): - enabled = field("m_Enabled", bool) - lightmap_index = field("m_LightmapIndex") - materials = field("m_Materials") - probe_anchor = field("m_ProbeAnchor") - receive_shadows = field("m_ReceiveShadows", bool) - reflection_probe_usage = field("m_ReflectionProbeUsage", ReflectionProbeUsage) - shadow_casting_mode = field("m_CastShadows", ShadowCastingMode) - sorting_layer_id = field("m_SortingLayerID") - sorting_order = field("m_SortingOrder") - use_light_probes = field("m_UseLightProbes", bool) - lightmap_index_dynamic = field("m_LightmapIndexDynamic") - lightmap_tiling_offset = field("m_LightmapTilingOffset") - lightmap_tiling_offset_dynamic = field("m_LightmapTilingOffsetDynamic") - static_batch_root = field("m_StaticBatchRoot") - subset_indices = field("m_SubsetIndices") - - @property - def material(self): - return self.materials[0] - - -class ParticleSystemRenderMode(IntEnum): - Billboard = 0 - Stretch = 1 - HorizontalBillboard = 2 - VerticalBillboard = 3 - Mesh = 4 - - -class ParticleSystemSortMode(IntEnum): - None_ = 0 - Distance = 1 - OldestInFront = 2 - YoungestInFront = 3 - - -class MeshRenderer(Component): - pass - - -class ParticleRenderer(Renderer): - camera_velocity_scale = field("m_CameraVelocityScale") - length_scale = field("m_LengthScale") - max_particle_size = field("m_MaxParticleSize") - velocity_scale = field("m_VelocityScale") - stretch_particles = field("m_StretchParticles") - uv_animation = field("UV Animation") - - -class ParticleSystemRenderer(Renderer): - camera_velocity_scale = field("m_CameraVelocityScale") - length_scale = field("m_LengthScale") - max_particle_size = field("m_MaxParticleSize") - mesh = field("m_Mesh") - mesh1 = field("m_Mesh1") - mesh2 = field("m_Mesh2") - mesh3 = field("m_Mesh3") - normal_direction = field("m_NormalDirection") - render_mode = field("m_RenderMode", ParticleSystemRenderMode) - sort_mode = field("m_SortMode", ParticleSystemSortMode) - sorting_fudge = field("m_SortingFudge") - velocity_scale = field("m_VelocityScale") diff --git a/unitypack/engine/text.py b/unitypack/engine/text.py deleted file mode 100644 index af2b3691..00000000 --- a/unitypack/engine/text.py +++ /dev/null @@ -1,65 +0,0 @@ -from enum import IntEnum - -from .component import Component -from .object import Object, field - - -class FontStyle(IntEnum): - Normal = 0 - Bold = 1 - Italic = 2 - BoldAndItalic = 3 - - -class TextAlignment(IntEnum): - Left = 0 - Center = 1 - Right = 2 - - -class TextAnchor(IntEnum): - UpperLeft = 0 - UpperCenter = 1 - UpperRight = 2 - MiddleLeft = 3 - MiddleCenter = 4 - MiddleRight = 5 - LowerLeft = 6 - LowerCenter = 7 - LoweRight = 8 - - -class TextMesh(Component): - alignment = field("m_Alignment", TextAlignment) - anchor = field("m_Anchor", TextAnchor) - character_size = field("m_CharacterSize") - color = field("m_Color") - font_size = field("m_FontSize") - font = field("m_Font") - font_style = field("m_FontStyle", FontStyle) - line_spacing = field("m_LineSpacing") - offset_z = field("m_OffsetZ") - rich_text = field("m_RichText", bool) - tab_size = field("m_TabSize") - text = field("m_Text") - - def __str__(self): - return self.text - - -class TextAsset(Object): - path = field("m_PathName") - script = field("m_Script") - - @property - def bytes(self): - return self.script - - @property - def text(self): - return self.bytes.decode("utf-8") - - -class Shader(Object): - dependencies = field("m_Dependencies") - script = field("m_Script") diff --git a/unitypack/engine/texture.py b/unitypack/engine/texture.py deleted file mode 100644 index a6aad4c4..00000000 --- a/unitypack/engine/texture.py +++ /dev/null @@ -1,257 +0,0 @@ -import logging -from enum import IntEnum - -from .object import Object, field - - -class TextureFormat(IntEnum): - Alpha8 = 1 - ARGB4444 = 2 - RGB24 = 3 - RGBA32 = 4 - ARGB32 = 5 - RGB565 = 7 - - # Direct3D - DXT1 = 10 - DXT5 = 12 - - RGBA4444 = 13 - BGRA32 = 14 - - BC6H = 24 - BC7 = 25 - - DXT1Crunched = 28 - DXT5Crunched = 29 - - # PowerVR - PVRTC_RGB2 = PVRTC_2BPP_RGB = 30 - PVRTC_RGBA2 = PVRTC_2BPP_RGBA = 31 - PVRTC_RGB4 = PVRTC_4BPP_RGB = 32 - PVRTC_RGBA4 = PVRTC_4BPP_RGBA = 33 - - # Ericsson (Android) - ETC_RGB4 = 34 - ATC_RGB4 = 35 - ATC_RGBA8 = 36 - - # Adobe ATF - ATF_RGB_DXT1 = 38 - ATF_RGBA_JPG = 39 - ATF_RGB_JPG = 40 - - # Ericsson - EAC_R = 41 - EAC_R_SIGNED = 42 - EAC_RG = 43 - EAC_RG_SIGNED = 44 - ETC2_RGB = 45 - ETC2_RGBA1 = 46 - ETC2_RGBA8 = 47 - - # OpenGL / GLES - ASTC_RGB_4x4 = 48 - ASTC_RGB_5x5 = 49 - ASTC_RGB_6x6 = 50 - ASTC_RGB_8x8 = 51 - ASTC_RGB_10x10 = 52 - ASTC_RGB_12x12 = 53 - ASTC_RGBA_4x4 = 54 - ASTC_RGBA_5x5 = 55 - ASTC_RGBA_6x6 = 56 - ASTC_RGBA_8x8 = 57 - ASTC_RGBA_10x10 = 58 - ASTC_RGBA_12x12 = 59 - - @property - def pixel_format(self): - if self == TextureFormat.RGB24: - return "RGB" - elif self == TextureFormat.ARGB32: - return "ARGB" - elif self == TextureFormat.RGB565: - return "RGB;16" - elif self == TextureFormat.Alpha8: - return "A" - elif self == TextureFormat.RGBA4444: - return "RGBA;4B" - elif self == TextureFormat.ARGB4444: - return "RGBA;4B" - return "RGBA" - - -IMPLEMENTED_FORMATS = ( - TextureFormat.Alpha8, - TextureFormat.ARGB4444, - TextureFormat.RGBA4444, - TextureFormat.RGB565, - TextureFormat.RGB24, - TextureFormat.RGBA32, - TextureFormat.ARGB32, - TextureFormat.DXT1, - TextureFormat.DXT1Crunched, - TextureFormat.DXT5, - TextureFormat.DXT5Crunched, - TextureFormat.BC7, - TextureFormat.ASTC_RGB_4x4, - TextureFormat.ASTC_RGB_5x5, - TextureFormat.ASTC_RGB_5x5, - TextureFormat.ASTC_RGB_6x6, - TextureFormat.ASTC_RGB_8x8, - TextureFormat.ASTC_RGB_10x10, - TextureFormat.ASTC_RGB_12x12, - TextureFormat.ASTC_RGBA_4x4, - TextureFormat.ASTC_RGBA_5x5, - TextureFormat.ASTC_RGBA_5x5, - TextureFormat.ASTC_RGBA_6x6, - TextureFormat.ASTC_RGBA_8x8, - TextureFormat.ASTC_RGBA_10x10, - TextureFormat.ASTC_RGBA_12x12 -) - - -class Sprite(Object): - border = field("m_Border") - extrude = field("m_Extrude") - offset = field("m_Offset") - rd = field("m_RD") - rect = field("m_Rect") - pixels_per_unit = field("m_PixelsToUnits") - - -class Material(Object): - global_illumination_flags = field("m_LightmapFlags") - render_queue = field("m_CustomRenderQueue") - shader = field("m_Shader") - shader_keywords = field("m_ShaderKeywords") - - @property - def saved_properties(self): - def _unpack_prop(value): - for vk, vv in value: - if isinstance(vk, str): # Unity 5.6+ - yield vk, vv - else: # Unity <= 5.4 - yield vk["name"], vv - return {k: dict(_unpack_prop(v)) for k, v in self._obj["m_SavedProperties"].items()} - - -class Texture(Object): - height = field("m_Height") - width = field("m_Width") - - -class Texture2D(Texture): - data = field("image data") - lightmap_format = field("m_LightmapFormat") - texture_settings = field("m_TextureSettings") - color_space = field("m_ColorSpace") - is_readable = field("m_IsReadable") - read_allowed = field("m_ReadAllowed") - format = field("m_TextureFormat", TextureFormat) - texture_dimension = field("m_TextureDimension") - mipmap = field("m_MipMap") - complete_image_size = field("m_CompleteImageSize") - stream_data = field("m_StreamData", default=False) - - def __repr__(self): - return "<%s %s (%s %ix%i)>" % ( - self.__class__.__name__, self.name, self.format.name, self.width, self.height - ) - - @property - def image_data(self): - if self.stream_data and self.stream_data.asset: - if not hasattr(self, "_data"): - self._data = self.stream_data.get_data() - return self._data - return self.data - - @property - def image(self): - from PIL import Image - from decrunch import File as CrunchFile - import astc_decomp - - if self.format not in IMPLEMENTED_FORMATS: - raise NotImplementedError("Unimplemented format %r" % (self.format)) - - if self.format in (TextureFormat.DXT1, TextureFormat.DXT1Crunched): - codec = "bcn" - args = (1, ) - elif self.format in (TextureFormat.DXT5, TextureFormat.DXT5Crunched): - codec = "bcn" - args = (3, ) - elif self.format == TextureFormat.BC7: - codec = "bcn" - args = (7, ) - # 4 5 6 8 10 12 - elif self.format == TextureFormat.ASTC_RGB_4x4: - codec = "astc" - args = (4, 4, False) - elif self.format == TextureFormat.ASTC_RGB_5x5: - codec = "astc" - args = (5, 5, False) - elif self.format == TextureFormat.ASTC_RGB_6x6: - codec = "astc" - args = (6, 6, False) - elif self.format == TextureFormat.ASTC_RGB_8x8: - codec = "astc" - args = (8, 8, False) - elif self.format == TextureFormat.ASTC_RGB_10x10: - codec = "astc" - args = (10, 10, False) - elif self.format == TextureFormat.ASTC_RGB_12x12: - codec = "astc" - args = (12, 12, False) - - elif self.format == TextureFormat.ASTC_RGBA_4x4: - codec = "astc" - args = (4, 4, True) - elif self.format == TextureFormat.ASTC_RGBA_5x5: - codec = "astc" - args = (5, 5, True) - elif self.format == TextureFormat.ASTC_RGBA_6x6: - codec = "astc" - args = (6, 6, True) - elif self.format == TextureFormat.ASTC_RGBA_8x8: - codec = "astc" - args = (8, 8, True) - elif self.format == TextureFormat.ASTC_RGBA_10x10: - codec = "astc" - args = (10, 10, True) - elif self.format == TextureFormat.ASTC_RGBA_12x12: - codec = "astc" - args = (12, 12, True) - else: - codec = "raw" - args = (self.format.pixel_format, ) - - mode = "RGB" if self.format.pixel_format in ("RGB", "RGB;16") else "RGBA" - size = (self.width, self.height) - - data = self.image_data - if self.format in (TextureFormat.DXT1Crunched, TextureFormat.DXT5Crunched): - data = CrunchFile(self.image_data).decode_level(0) - - # Pillow wants bytes, not bytearrays - data = bytes(data) - - if not data and size == (0, 0): - return None - - return Image.frombytes(mode, size, data, codec, args) - - -class StreamingInfo(Object): - offset = field("offset") - size = field("size") - path = field("path") - - def get_data(self): - if not self.asset: - logging.warning("No data available for StreamingInfo") - return b"" - self.asset._buf.seek(self.asset._buf_ofs + self.offset) - return self.asset._buf.read(self.size) diff --git a/unitypack/enums.py b/unitypack/enums.py deleted file mode 100644 index 3bc0a276..00000000 --- a/unitypack/enums.py +++ /dev/null @@ -1,50 +0,0 @@ -from enum import IntEnum - - -class CompressionType(IntEnum): - NONE = 0 - LZMA = 1 - LZ4 = 2 - LZ4HC = 3 - LZHAM = 4 - - -class NodeFlags(IntEnum): - Default = 0 - Directory = 1 - Deleted = 2 - SerializedFile = 3 - - -class RuntimePlatform(IntEnum): - OSXEditor = 0 - OSXPlayer = 1 - WindowsPlayer = 2 - OSXWebPlayer = 3 - OSXDashboardPlayer = 4 - WindowsWebPlayer = 5 - WindowsEditor = 7 - IPhonePlayer = 8 - PS3 = 9 - XBOX360 = 10 - Android = 11 - NaCl = 12 - LinuxPlayer = 13 - FlashPlayer = 15 - WebGLPlayer = 17 - MetroPlayerX86 = 18 - WSAPlayerX86 = 18 - MetroPlayerX64 = 19 - WSAPlayerX64 = 19 - MetroPlayerARM = 20 - WSAPlayerARM = 20 - WP8Player = 21 - BB10Player = 22 - BlackBerryPlayer = 22 - TizenPlayer = 23 - PSP2 = 24 - PS4 = 25 - PSM = 26 - PSMPlayer = 26 - XboxOne = 27 - SamsungTVPlayer = 28 diff --git a/unitypack/environment.py b/unitypack/environment.py deleted file mode 100644 index 8971de87..00000000 --- a/unitypack/environment.py +++ /dev/null @@ -1,87 +0,0 @@ -import os -from urllib.parse import urlparse - -from .asset import Asset -from .assetbundle import AssetBundle -from .exceptions import ArchiveNotFound - - -class UnityEnvironment: - def __init__(self, base_path=""): - self.bundles = {} - self.assets = {} - self.base_path = base_path - self.files = [] - - def __del__(self): - for f in self.files: - f.close() - - def __repr__(self): - return "%s(base_path=%r)" % (self.__class__.__name__, self.base_path) - - def load(self, file): - for bundle in self.bundles.values(): - if os.path.abspath(file.name) == os.path.abspath(bundle.path): - return bundle - ret = AssetBundle(self) - ret.load(file) - self.bundles[ret.name.lower()] = ret - for asset in ret.assets: - self.assets[asset.name.lower()] = asset - return ret - - def discover(self, name): - for bundle in list(self.bundles.values()): - dirname = os.path.dirname(os.path.abspath(bundle.path)) - for filename in os.listdir(dirname): - basename = os.path.splitext(os.path.basename(filename))[0] - if name.lower() == "cab-" + basename.lower(): - f = open(os.path.join(dirname, filename), "rb") - self.files.append(f) - self.load(f) - - def get_asset_by_filename(self, name): - if name not in self.assets: - path = os.path.join(self.base_path, name) - if os.path.exists(path): - f = open(path, "rb") - self.files.append(f) - self.assets[name] = Asset.from_file(f) - else: - self.discover(name) - self.populate_assets() - if name not in self.assets: - raise KeyError("No such asset: %r" % (name)) - return self.assets[name] - - def populate_assets(self): - for bundle in self.bundles.values(): - for asset in bundle.assets: - asset_name = asset.name.lower() - if asset_name not in self.assets: - self.assets[asset_name] = asset - - def get_asset(self, url): - if not url: - return None - - u = urlparse(url) - if u.scheme == "archive": - archive, name = os.path.split(u.path.lstrip("/").lower()) - else: - raise NotImplementedError("Unsupported scheme: %r" % (u.scheme)) - - if archive not in self.bundles: - self.discover(archive) - - # Still didn't find it? Give up... - if archive not in self.bundles: - raise ArchiveNotFound("Cannot find %r in %r" % (archive, self.bundles)) - - bundle = self.bundles[archive] - - for asset in bundle.assets: - if asset.name.lower() == name: - return asset - raise KeyError("No such asset: %r" % (name)) diff --git a/unitypack/exceptions.py b/unitypack/exceptions.py deleted file mode 100644 index 0782acbc..00000000 --- a/unitypack/exceptions.py +++ /dev/null @@ -1,5 +0,0 @@ -class UnityPackException(Exception): - pass - -class ArchiveNotFound(UnityPackException): - pass diff --git a/unitypack/export.py b/unitypack/export.py deleted file mode 100644 index 5337bbb5..00000000 --- a/unitypack/export.py +++ /dev/null @@ -1,197 +0,0 @@ -from io import BytesIO - -from .utils import BinaryReader - - -class OBJVector2: - def __init__(self, x=0, y=0): - self.x = x - self.y = y - - def read(self, buf): - self.x = buf.read_float() - self.y = buf.read_float() - return self - - def __str__(self): - return "%s %s" % (self.x, 1 - self.y) - - -class OBJVector3(OBJVector2): - def __init__(self, x=0, y=0, z=0): - super().__init__(x, y) - self.z = z - - def read(self, buf): - super().read(buf) - self.z = buf.read_float() - return self - - def __str__(self): - return "%s %s %s" % (-self.x, self.y, self.z) - - -class OBJVector4(OBJVector3): - def __init__(self, x=0, y=0, z=0, w=0): - super().__init__(x, y, z) - self.w = w - - def read(self, buf): - super().read(buf) - self.w = buf.read_float() - return self - - def read_color(self, buf): - self.x = buf.read_ubyte() - self.y = buf.read_ubyte() - self.z = buf.read_ubyte() - self.w = buf.read_ubyte() - return self - - def __str__(self): - return "%s %s %s %s" % (self.x, self.y, self.z, self.w) - - -class MeshData: - def __init__(self, mesh): - self.mesh = mesh - self.indices = [] - self.triangles = [] - self.vertices = [] - self.normals = [] - self.colors = [] - self.uv1 = [] - self.uv2 = [] - self.uv3 = [] - self.uv4 = [] - self.tangents = [] - self.extract_indices() - self.extract_vertices() - - def extract_indices(self): - for sub in self.mesh.submeshes: - sub_indices = [] - sub_triangles = [] - buf = BinaryReader(BytesIO(self.mesh.index_buffer)) - buf.seek(sub.first_byte) - for i in range(0, sub.index_count): - sub_indices.append(buf.read_uint16()) - if not sub.topology: - sub_triangles.extend(sub_indices) - else: - raise NotImplementedError("(%s) topologies are not supported" % (self.mesh.name)) - - self.indices.append(sub_indices) - self.triangles.append(sub_triangles) - - def extract_vertices(self): - # unity 5+ has 8 channels (6 otherwise) - v5_channel_count = 8 - buf = BinaryReader(BytesIO(self.mesh.vertex_data.data)) - channels = self.mesh.vertex_data.channels - # actual streams attribute 'm_Streams' may only exist in unity 4, - # use of channel data alone seems to be sufficient - stream_count = self.get_num_streams(channels) - channel_count = len(channels) - - for s in range(0, stream_count): - for i in range(0, self.mesh.vertex_data.vertex_count): - for j in range(0, channel_count): - ch = None - if channel_count > 0: - ch = channels[j] - # format == 1, use half-floats (16 bit) - if ch["format"] == 1: - raise NotImplementedError("(%r) 16 bit floats are not supported" % (self.mesh)) - # read the appropriate vertex value into the correct list - if ch and ch["dimension"] > 0 and ch["stream"] == s: - if j == 0: - self.vertices.append(OBJVector3().read(buf)) - elif j == 1: - self.normals.append(OBJVector3().read(buf)) - elif j == 2: - self.colors.append(OBJVector4().read_color(buf)) - elif j == 3: - self.uv1.append(OBJVector2().read(buf)) - elif j == 4: - self.uv2.append(OBJVector2().read(buf)) - elif j == 5: - if channel_count == v5_channel_count: - self.uv3.append(OBJVector2().read(buf)) - else: - self.tangents.append(OBJVector4().read(buf)) - elif j == 6: # for unity 5+ - self.uv4.append(OBJVector2().read(buf)) - elif j == 7: # for unity 5+ - self.tangents.append(OBJVector4().read(buf)) - # TODO investigate possible alignment here, after each stream - - def get_num_streams(self, channels): - streams = [] - # scan the channel's stream value for distinct entries - for c in channels: - if c["stream"] not in streams: - streams.append(c["stream"]) - - return len(streams) - - -class OBJMesh: - def __init__(self, mesh): - if mesh.mesh_compression: - # TODO handle compressed meshes - raise NotImplementedError("(%s) compressed meshes are not supported" % (mesh.name)) - self.mesh_data = MeshData(mesh) - self.mesh = mesh - - @staticmethod - def face_str(indices, coords, normals): - ret = ["f "] - for i in indices[::-1]: - ret.append(str(i + 1)) - if coords or normals: - ret.append("/") - if coords: - ret.append(str(i + 1)) - if normals: - ret.append("/") - ret.append(str(i + 1)) - ret.append(" ") - ret.append("\n") - return "".join(ret) - - def export(self): - ret = [] - verts_per_face = 3 - normals = self.mesh_data.normals - tex_coords = self.mesh_data.uv1 - if not tex_coords: - tex_coords = self.mesh_data.uv2 - - for v in self.mesh_data.vertices: - ret.append("v %s\n" % (v)) - for v in normals: - ret.append("vn %s\n" % (v)) - for v in tex_coords: - ret.append("vt %s\n" % (v)) - ret.append("\n") - - # write group name and set smoothing to 1 - ret.append("g %s\n" % (self.mesh.name)) - ret.append("s 1\n") - - sub_count = len(self.mesh.submeshes) - for i in range(0, sub_count): - if sub_count == 1: - ret.append("usemtl %s\n" % (self.mesh.name)) - else: - ret.append("usemtl %s_%d\n" % (self.mesh.name, i)) - face_tri = [] - for t in self.mesh_data.triangles[i]: - face_tri.append(t) - if len(face_tri) == verts_per_face: - ret.append(self.face_str(face_tri, tex_coords, normals)) - face_tri = [] - ret.append("\n") - - return "".join(ret) diff --git a/unitypack/object.py b/unitypack/object.py deleted file mode 100644 index bbbb1dad..00000000 --- a/unitypack/object.py +++ /dev/null @@ -1,232 +0,0 @@ -from collections import OrderedDict -from io import BytesIO - -from . import engine as UnityEngine -from .resources import UnityClass -from .type import TypeMetadata, TypeTree -from .utils import BinaryReader - - -def load_object(type, obj): - clsname = type.type - if hasattr(UnityEngine, clsname): - obj = getattr(UnityEngine, clsname)(obj) - - return obj - - -class ObjectInfo: - def __init__(self, asset): - self.asset = asset - - def __repr__(self): - return "<%s %i>" % (self.type, self.class_id) - - @property - def type(self): - if self.type_id > 0: - return UnityClass(self.type_id) - elif self.type_id not in self.asset.typenames: - script = self.read()["m_Script"] - if script: - try: - typename = script.resolve()["m_ClassName"] - except NotImplementedError: - typename = script.type.type[5:-1] # Capture type name in PPtr<...> - elif self.type_id in self.asset.tree.type_trees: - typename = self.asset.tree.type_trees[self.type_id].type - else: - typename = str(self.type_id) - self.asset.typenames[self.type_id] = typename - return self.asset.typenames[self.type_id] - - @property - def type_tree(self): - if self.type_id < 0: - type_trees = self.asset.tree.type_trees - if self.type_id in type_trees: - return type_trees[self.type_id] - elif self.class_id in type_trees: - return type_trees[self.class_id] - return TypeMetadata.default(self.asset).type_trees[self.class_id] - return self.asset.types[self.type_id] - - def load(self, buf): - self.path_id = self.read_id(buf) - self.data_offset = buf.read_uint() + self.asset.data_offset - self.size = buf.read_uint() - if self.asset.format < 17: - self.type_id = buf.read_int() - self.class_id = buf.read_int16() - else: - type_id = buf.read_int() - class_id = self.asset.tree.class_ids[type_id] - self.type_id = class_id - self.class_id = class_id - if self.asset.format <= 10: - self.is_destroyed = bool(buf.read_int16()) - if self.asset.format >= 11 and self.asset.format <= 16: - self.unk0 = buf.read_int16() - - if self.asset.format >= 15 and self.asset.format <= 16: - self.unk1 = buf.read_byte() - - def read_id(self, buf): - if self.asset.long_object_ids: - return buf.read_int64() - else: - return self.asset.read_id(buf) - - def read(self): - buf = self.asset._buf - buf.seek(self.asset._buf_ofs + self.data_offset) - object_buf = buf.read(self.size) - return self.read_value(self.type_tree, BinaryReader(BytesIO(object_buf))) - - def read_value(self, type, buf): - align = False - expected_size = type.size - pos_before = buf.tell() - t = type.type - first_child = type.children[0] if type.children else TypeTree(self.asset.format) - if t == "bool": - result = buf.read_boolean() - elif t == "SInt8": - result = buf.read_byte() - elif t == "UInt8": - result = buf.read_ubyte() - elif t == "SInt16": - result = buf.read_int16() - elif t == "UInt16": - result = buf.read_uint16() - elif t == "SInt64": - result = buf.read_int64() - elif t == "UInt64": - result = buf.read_int64() - elif t in ("UInt32", "unsigned int"): - result = buf.read_uint() - elif t in ("SInt32", "int"): - result = buf.read_int() - elif t == "float": - buf.align() - result = buf.read_float() - elif t == "double": - buf.align() - result = buf.read_double() - elif t == "string": - if type.size == -1: - size = buf.read_uint() - else: - size = type.size - result = buf.read_string(size) - align = type.children[0].post_align - else: - if type.is_array: - first_child = type - - if t.startswith("PPtr<"): - result = ObjectPointer(type, self.asset) - result.load(buf) - if not result: - result = None - - elif first_child and first_child.is_array: - align = first_child.post_align - size = buf.read_uint() - array_type = first_child.children[1] - if array_type.type in ("char", "UInt8"): - result = buf.read(size) - else: - result = [] - for i in range(size): - result.append(self.read_value(array_type, buf)) - elif t == "pair": - assert len(type.children) == 2 - first = self.read_value(type.children[0], buf) - second = self.read_value(type.children[1], buf) - result = (first, second) - elif t.startswith("ExposedReference"): - exposed_ref = ExposedReferenceInfo(self.asset) - result = OrderedDict() - - for child in type.children: - result[child.name] = exposed_ref.read_value(child, buf) - - result = load_object(type, result) - else: - result = OrderedDict() - - for child in type.children: - result[child.name] = self.read_value(child, buf) - - result = load_object(type, result) - if t == "StreamedResource": - result.asset = self.resolve_streaming_asset(result.source) - elif t == "StreamingInfo": - result.asset = self.resolve_streaming_asset(result.path) - - # Check to make sure we read at least as many bytes the tree says. - # We allow reading more for the case of alignment. - pos_after = buf.tell() - actual_size = pos_after - pos_before - if expected_size > 0 and actual_size < expected_size: - raise ValueError("Expected read_value(%r) to read %r bytes, but only read %r bytes" % (type, expected_size, actual_size)) - - if align or type.post_align: - buf.align() - - return result - - def resolve_streaming_asset(self, path): - if len(path) > 0: - return self.asset.get_asset(path) - - -class ExposedReferenceInfo(ObjectInfo): - - def read_value(self, type, buf): - if type.name == "exposedName": - buf.read_uint() - return "" - else: - return super().read_value(type, buf) - - -class ObjectPointer: - def __init__(self, type, asset): - self.type = type - self.source_asset = asset - - def __repr__(self): - return "%s(file_id=%r, path_id=%r)" % ( - self.__class__.__name__, self.file_id, self.path_id - ) - - def __bool__(self): - return not (self.file_id == 0 and self.path_id == 0) - - @property - def asset(self): - from .asset import AssetRef - - ret = self.source_asset.asset_refs[self.file_id] - if isinstance(ret, AssetRef): - ret = ret.resolve() - return ret - - @property - def object(self): - asset = self.asset - if asset is None: - return None - return asset.objects[self.path_id] - - def load(self, buf): - self.file_id = buf.read_int() - self.path_id = self.source_asset.read_id(buf) - - def resolve(self): - obj = self.object - if obj is None: - return None - return obj.read() diff --git a/unitypack/resources.py b/unitypack/resources.py deleted file mode 100644 index 3db38291..00000000 --- a/unitypack/resources.py +++ /dev/null @@ -1,18 +0,0 @@ -import json -import os - - -def get_resource(name): - return os.path.join(os.path.dirname(__file__), name) - - -with open(get_resource("strings.dat"), "rb") as f: - STRINGS_DAT = f.read() - - -with open(get_resource("classes.json"), "r") as f: - UNITY_CLASSES = json.load(f) - - -def UnityClass(i): - return UNITY_CLASSES.get(str(i), "" % (i)) diff --git a/unitypack/strings.dat b/unitypack/strings.dat deleted file mode 100644 index 1cfa4177..00000000 Binary files a/unitypack/strings.dat and /dev/null differ diff --git a/unitypack/structs.dat b/unitypack/structs.dat deleted file mode 100644 index 76cca476..00000000 Binary files a/unitypack/structs.dat and /dev/null differ diff --git a/unitypack/type.py b/unitypack/type.py deleted file mode 100644 index 20b50b69..00000000 --- a/unitypack/type.py +++ /dev/null @@ -1,164 +0,0 @@ -from io import BytesIO - -from .enums import RuntimePlatform -from .resources import STRINGS_DAT, get_resource -from .utils import BinaryReader - - -class TypeTree: - NULL = "(null)" - - def __init__(self, format): - self.children = [] - self.version = 0 - self.is_array = False - self.size = 0 - self.index = 0 - self.flags = 0 - self.type = self.NULL - self.name = self.NULL - self.format = format - - def __repr__(self): - return "<%s %s (size=%r, index=%r, is_array=%r, flags=%r)>" % ( - self.type, self.name, self.size, self.index, self.is_array, self.flags - ) - - @property - def post_align(self): - return bool(self.flags & 0x4000) - - def load(self, buf): - if self.format == 10 or self.format >= 12: - self.load_blob(buf) - else: - self.load_old(buf) - - def load_old(self, buf): - self.type = buf.read_string() - self.name = buf.read_string() - self.size = buf.read_int() - self.index = buf.read_int() - self.is_array = bool(buf.read_int()) - self.version = buf.read_int() - self.flags = buf.read_int() - - num_fields = buf.read_uint() - for i in range(num_fields): - tree = TypeTree(self.format) - tree.load(buf) - self.children.append(tree) - - def load_blob(self, buf): - num_nodes = buf.read_uint() - self.buffer_bytes = buf.read_uint() - node_bytes = 32 if self.format >= 19 else 24 - node_data = BytesIO(buf.read(node_bytes * num_nodes)) - self.data = buf.read(self.buffer_bytes) - - parents = [self] - - buf = BinaryReader(node_data) - - for i in range(num_nodes): - version = buf.read_int16() - depth = buf.read_ubyte() - - if depth == 0: - curr = self - else: - while len(parents) > depth: - parents.pop() - curr = TypeTree(self.format) - parents[-1].children.append(curr) - parents.append(curr) - - curr.version = version - curr.is_array = buf.read_byte() - curr.type = self.get_string(buf.read_int()) - curr.name = self.get_string(buf.read_int()) - curr.size = buf.read_int() - curr.index = buf.read_uint() - curr.flags = buf.read_int() - - # consume unused bytes in the node (always zeros?) - buf.read(node_bytes - 24) - - def get_string(self, offset): - if offset < 0: - offset &= 0x7fffffff - data = STRINGS_DAT - elif offset < self.buffer_bytes: - data = self.data - else: - return self.NULL - return data[offset:].partition(b"\0")[0].decode("utf-8") - - -class TypeMetadata: - default_instance = None - - @classmethod - def default(cls, asset): - if not cls.default_instance: - cls.default_instance = cls(asset) - with open(get_resource("structs.dat"), "rb") as f: - cls.default_instance.load(BinaryReader(f), format=15) - return cls.default_instance - - def __init__(self, asset): - self.class_ids = [] - self.type_trees = {} - self.hashes = {} - self.asset = asset - self.generator_version = "" - self.target_platform = None - - def load(self, buf, format=None): - if format is None: - format = self.asset.format - self.generator_version = buf.read_string() - self.target_platform = RuntimePlatform(buf.read_uint()) - - if format >= 13: - has_type_trees = buf.read_boolean() - num_types = buf.read_int() - - for i in range(num_types): - class_id = buf.read_int() - if format >= 17: - unk0 = buf.read_byte() - script_id = buf.read_int16() - if class_id == 114: - if script_id >= 0: - # make up a fake negative class_id to work like the - # old system. class_id of -1 is taken to mean that - # the MonoBehaviour base class was serialized; that - # shouldn't happen, but it's easy to account for. - class_id = -2 - script_id - else: - class_id = -1 - self.class_ids.append(class_id) - if class_id < 0: - hash = buf.read(0x20) - else: - hash = buf.read(0x10) - - self.hashes[class_id] = hash - - if has_type_trees: - tree = TypeTree(format) - tree.load(buf) - self.type_trees[class_id] = tree - - # 4 unidentified bytes at the end of a type tree in 2019.4 - if format >= 21: - unk1 = buf.read(4) - - else: - num_fields = buf.read_int() - for i in range(num_fields): - class_id = buf.read_int() - tree = TypeTree(format) - tree.load(buf) - self.type_trees[class_id] = tree diff --git a/unitypack/utils.py b/unitypack/utils.py deleted file mode 100644 index 0bcca76b..00000000 --- a/unitypack/utils.py +++ /dev/null @@ -1,114 +0,0 @@ -import struct -from os import SEEK_CUR - - -def lz4_decompress(data, size): - try: - from lz4.block import decompress - except ImportError: - raise RuntimeError("python-lz4 >= 0.9 is required to read UnityFS files") - - return decompress(data, size) - - -def extract_audioclip_samples(d) -> dict: - """ - Extract all the sample data from an AudioClip and - convert it from FSB5 if needed. - """ - ret = {} - - if not d.data: - # eg. StreamedResource not available - return {} - - try: - from fsb5 import FSB5 - except ImportError as e: - raise RuntimeError("python-fsb5 is required to extract AudioClip") - - af = FSB5(d.data) - for i, sample in enumerate(af.samples): - if i > 0: - filename = "%s-%i.%s" % (d.name, i, af.get_sample_extension()) - else: - filename = "%s.%s" % (d.name, af.get_sample_extension()) - try: - sample = af.rebuild_sample(sample) - except ValueError as e: - print("WARNING: Could not extract %r (%s)" % (d, e)) - continue - ret[filename] = sample - - return ret - - -class BinaryReader: - def __init__(self, buf, endian="<"): - self.buf = buf - self.endian = endian - - def align(self): - old = self.tell() - new = (old + 3) & -4 - if new > old: - self.seek(new - old, SEEK_CUR) - - def read(self, *args): - return self.buf.read(*args) - - def seek(self, *args): - return self.buf.seek(*args) - - def tell(self): - return self.buf.tell() - - def read_string(self, size=None, encoding="utf-8"): - if size is None: - ret = self.read_cstring() - else: - ret = struct.unpack(self.endian + "%is" % (size), self.read(size))[0] - try: - return ret.decode(encoding) - except UnicodeDecodeError: - return ret - - def read_cstring(self) -> bytes: - ret = [] - c = b"" - while c != b"\0": - ret.append(c) - c = self.read(1) - if not c: - raise ValueError("Unterminated string: %r" % (ret)) - return b"".join(ret) - - def read_boolean(self) -> bool: - return bool(struct.unpack(self.endian + "b", self.read(1))[0]) - - def read_byte(self) -> int: - return struct.unpack(self.endian + "b", self.read(1))[0] - - def read_ubyte(self) -> int: - return struct.unpack(self.endian + "B", self.read(1))[0] - - def read_int16(self) -> int: - return struct.unpack(self.endian + "h", self.read(2))[0] - - def read_uint16(self) -> int: - return struct.unpack(self.endian + "H", self.read(2))[0] - - def read_int(self) -> int: - return struct.unpack(self.endian + "i", self.read(4))[0] - - def read_uint(self) -> int: - return struct.unpack(self.endian + "I", self.read(4))[0] - - def read_float(self) -> float: - return struct.unpack(self.endian + "f", self.read(4))[0] - - def read_double(self) -> float: - return struct.unpack(self.endian + "d", self.read(8))[0] - - def read_int64(self) -> int: - return struct.unpack(self.endian + "q", self.read(8))[0]