├── assets └── Delete Me Please.txt ├── RepackAssets.py ├── UnpackAssets.py ├── .gitignore ├── README.md ├── BinaryHelper.py ├── classidFormat.pydat └── LibUnity.py /assets/Delete Me Please.txt: -------------------------------------------------------------------------------- 1 | Delete Me Please 2 | -------------------------------------------------------------------------------- /RepackAssets.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import traceback 3 | import os 4 | import LibUnity 5 | 6 | 7 | def main(): 8 | print("Unity Assets Unpacker") 9 | if not os.path.exists('assets/'): 10 | print('NO assets folder found,please put .assets into assets folder') 11 | os.makedirs('assets/') 12 | fl = os.listdir('assets') 13 | for fn in fl: 14 | if (os.path.isfile("assets/%s" % fn)): 15 | if ("level" in fn or ".assets" in fn): 16 | assetsReader = LibUnity.AssetsLoader("assets/%s" % fn) 17 | assetsReader.Pack("assets/%s_unpacked" % fn) 18 | 19 | if __name__ == '__main__': 20 | try: 21 | main() 22 | except: 23 | traceback.print_exc() 24 | -------------------------------------------------------------------------------- /UnpackAssets.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import traceback 3 | import os 4 | import LibUnity 5 | 6 | 7 | def main(): 8 | print("Unity Assets Unpacker") 9 | if not os.path.exists('assets/'): 10 | print('NO assets folder found,please put .assets into assets folder') 11 | os.makedirs('assets/') 12 | fl = os.listdir('assets') 13 | for fn in fl: 14 | if (os.path.isfile("assets/%s" % fn)): 15 | if ("level" in fn or ".assets" in fn): 16 | print(fn) 17 | assetsReader = LibUnity.AssetsLoader("assets/%s" % fn) 18 | assetsReader.Unpack("assets/%s_unpacked" % fn) 19 | 20 | 21 | if __name__ == '__main__': 22 | try: 23 | main() 24 | except: 25 | traceback.print_exc() 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity3D-Assets-Unpacker 2 | Unity3D Assets Unpacker 3 | 4 | ''' Created on 2015-8-11 @author: wmltogether ''' 5 | 6 | 说明:在Unity 5.5+中变更了Class ID reference 结构,对于纹理格式,纹理数据也被转移到了resS中进行保存。 7 | 8 | 9 | 10 | ''' 11 | ver 20170518 12 | >更新了unity5.5文件类型的获取方式 13 | 14 | ver 20161216 15 | 16 | >修复unity5.5索引对齐导致解包失败的问题 17 | 18 | ver 20161216 19 | 20 | >重构版本,增加unity5.5支持 21 | 22 | ver 20150811 23 | 24 | >修正U3D版本判断 25 | 26 | ver 20150727 27 | 28 | >增加Debug output 29 | 30 | >增加Classid判断 31 | 32 | ver 20150414 33 | 34 | >增加Unity 版本判断机制 35 | 36 | ver 20150323 37 | 38 | >添加Unity 5支援 39 | 40 | >移除Unity 4支援 41 | 42 | ver 20150127 43 | 44 | >增加了Texture Sprite扩展名(tmsk) 45 | 46 | ver 20141128 47 | 48 | >增加了非法文件名判断 49 | 50 | ver 20141103 51 | 52 | >增加了版本号地址判断 53 | 54 | ver 20140827 55 | 56 | >修改了textassets的扩展名 57 | 58 | ver 20140815 59 | 60 | >目前仅支持unity 4.x assets格式 61 | 62 | >依然兼容旧版unity打包脚本 63 | 64 | >添加了文件格式的简单判断,目前支持最基础的图片音频纯文本类型支持,判断规则详见 65 | 66 | http://docs.unity3d.com/Documentation/Manual/ClassIDReference.html 67 | 68 | >添加文件名unicode判断,使用异常判断来忽略非ascii文件名 69 | 70 | >封包会自动过滤文件夹中的png格式 71 | 72 | >split文件数量计算函数 73 | 74 | >提供了OBB文件md5校验计算:getObbMD5 75 | 76 | 77 | 78 | ''' 79 | -------------------------------------------------------------------------------- /BinaryHelper.py: -------------------------------------------------------------------------------- 1 | # /usr/bin/python2 2 | import struct 3 | 4 | 5 | class BinaryReader: 6 | 7 | def __init__(self, base_stream): 8 | self.base_stream = base_stream 9 | 10 | def Seek(self, position, seekorigin=0): 11 | return self.base_stream.seek(position, seekorigin) 12 | 13 | def Tell(self): 14 | return self.base_stream.tell() 15 | 16 | def ReadByte(self): 17 | return self.base_stream.read(1) 18 | 19 | def ReadBytes(self, count): 20 | return self.base_stream.read(count) 21 | 22 | def ReadChar(self): 23 | return ord(self.base_stream.read(1)) 24 | 25 | def ReadChars(self, count): 26 | return struct.unpack('%dB', self.base_steam.read(count)) 27 | 28 | def ReadInt16(self): 29 | return struct.unpack('h', self.base_stream.read(2))[0] 30 | 31 | def ReadInt32(self): 32 | return struct.unpack('i', self.base_stream.read(4))[0] 33 | 34 | def ReadInt64(self): 35 | return struct.unpack('q', self.base_stream.read(8))[0] 36 | 37 | def ReadUInt16(self): 38 | return struct.unpack('H', self.base_stream.read(2))[0] 39 | 40 | def ReadUInt32(self): 41 | return struct.unpack('I', self.base_stream.read(4))[0] 42 | 43 | def ReadUInt64(self): 44 | return struct.unpack('Q', self.base_stream.read(8))[0] 45 | 46 | def ReadFloat(self): 47 | return struct.unpack('f', self.base_stream.read(4))[0] 48 | 49 | def ReadBEInt16(self): 50 | return struct.unpack('>h', self.base_stream.read(2))[0] 51 | 52 | def ReadBEInt32(self): 53 | return struct.unpack('>i', self.base_stream.read(4))[0] 54 | 55 | def ReadBEInt64(self): 56 | return struct.unpack('>q', self.base_stream.read(8))[0] 57 | 58 | def ReadBEUInt16(self): 59 | return struct.unpack('>H', self.base_stream.read(2))[0] 60 | 61 | def ReadBEUInt32(self): 62 | return struct.unpack('>I', self.base_stream.read(4))[0] 63 | 64 | def ReadBEUInt64(self): 65 | return struct.unpack('>Q', self.base_stream.read(8))[0] 66 | 67 | def Read7BitEncodedInt(self): 68 | a, b, c = 0, 0, 0 69 | mask = 0x7f 70 | shift = 7 71 | while c < 5: 72 | d = ord(self.ReadByte()) 73 | a |= (d & mask) << b 74 | b += shift 75 | if ((d & (mask + 1)) == 0): 76 | break 77 | c += 1 78 | 79 | if c >= 5: 80 | raise NameError, ("Too many bytes in 7 bit encoded Int32") 81 | return a 82 | 83 | def ReadDotNETString(self): 84 | capacity = self.Read7BitEncodedInt() 85 | if capacity < 0: 86 | raise IOError, ("Invalid binary file (string len < 0)") 87 | if capacity == 0: 88 | return "" 89 | string_buffer = self.ReadBytes(capacity) 90 | return string_buffer 91 | 92 | def ReadCString(self): 93 | string = "" 94 | c = True 95 | while (c): 96 | var = self.ReadByte() 97 | if not var: 98 | break 99 | if (var != "\x00"): 100 | string += var 101 | else: 102 | self.Seek(-1, 1) 103 | c = False 104 | break 105 | return string 106 | 107 | 108 | class BinaryWriter: 109 | 110 | def __init__(self, base_stream): 111 | self.base_stream = base_stream 112 | 113 | def Seek(self, position, seekorigin=0): 114 | return self.base_stream.seek(position, seekorigin) 115 | 116 | def Tell(self): 117 | return self.base_stream.tell() 118 | 119 | def WriteByte(self, value): 120 | self.base_stream.write(value[0]) 121 | 122 | def FillBytes(self, value, count): 123 | if count <= 0: 124 | count = 0 125 | if len(value) <= count: 126 | value += '\x00' * (count - len(value)) 127 | self.base_stream.write(value[:count]) 128 | 129 | def Write7BitEncodedInt(self, value): 130 | if value == 0: 131 | self.WriteByte(chr(value)) 132 | while value != 0: 133 | num = (value >> 7) & 0x1ffffff 134 | num2 = value & 0x7f 135 | if num != 0: 136 | num2 = num2 | 0x80 137 | self.WriteByte(chr(num2)) 138 | value = num 139 | 140 | def WriteDotNETString(self, value): 141 | byteCount = len(value) 142 | self.Write7BitEncodedInt(byteCount) 143 | self.WriteBytes(value, len(value)) 144 | -------------------------------------------------------------------------------- /classidFormat.pydat: -------------------------------------------------------------------------------- 1 | 1 GameObject GameObject 2 | 2 Component Component 3 | 3 LevelGameManager LevelGameManager 4 | 4 Transform Transform 5 | 5 TimeManager TimeManager 6 | 6 GlobalGameManager GlobalGameManager 7 | 8 Behaviour Behaviour 8 | 9 GameManager GameManager 9 | 11 AudioManager AudioManager 10 | 12 ParticleAnimator ParticleAnimator 11 | 13 InputManager InputManager 12 | 15 EllipsoidParticleEmitter EllipsoidParticleEmitter 13 | 17 Pipeline Pipeline 14 | 18 EditorExtension EditorExtension 15 | 19 Physics2DSettings Physics2DSettings 16 | 20 Camera Camera 17 | 21 Material Material 18 | 23 MeshRenderer MeshRenderer 19 | 25 Renderer Renderer 20 | 26 ParticleRenderer ParticleRenderer 21 | 27 Texture tex 22 | 28 Texture2D tex 23 | 29 SceneSettings SceneSettings 24 | 30 GraphicsSettings GraphicsSettings 25 | 33 MeshFilter MeshFilter 26 | 41 OcclusionPortal OcclusionPortal 27 | 43 Mesh Mesh 28 | 45 Skybox Skybox 29 | 47 QualitySettings QualitySettings 30 | 48 Shader Shader 31 | 49 TextAsset TextAsset 32 | 50 Rigidbody2D Rigidbody2D 33 | 51 Physics2DManager Physics2DManager 34 | 53 Collider2D Collider2D 35 | 54 Rigidbody Rigidbody 36 | 55 PhysicsManager PhysicsManager 37 | 56 Collider Collider 38 | 57 Joint Joint 39 | 58 CircleCollider2D CircleCollider2D 40 | 59 HingeJoint HingeJoint 41 | 60 PolygonCollider2D PolygonCollider2D 42 | 61 BoxCollider2D BoxCollider2D 43 | 62 PhysicsMaterial2D PhysicsMaterial2D 44 | 64 MeshCollider MeshCollider 45 | 65 BoxCollider BoxCollider 46 | 66 SpriteCollider2D SpriteCollider2D 47 | 68 EdgeCollider2D EdgeCollider2D 48 | 72 ComputeShader ComputeShader 49 | 74 AnimationClip AnimationClip 50 | 75 ConstantForce ConstantForce 51 | 76 WorldParticleCollider WorldParticleCollider 52 | 78 TagManager TagManager 53 | 81 AudioListener AudioListener 54 | 82 AudioSource AudioSource 55 | 83 AudioClip AudioClip 56 | 84 RenderTexture RenderTexture 57 | 87 MeshParticleEmitter MeshParticleEmitter 58 | 88 ParticleEmitter ParticleEmitter 59 | 89 Cubemap Cubemap 60 | 90 Avatar Avatar 61 | 91 AnimatorController AnimatorController 62 | 92 GUILayer GUILayer 63 | 93 RuntimeAnimatorController RuntimeAnimatorController 64 | 94 ScriptMapper ScriptMapper 65 | 95 Animator Animator 66 | 96 TrailRenderer TrailRenderer 67 | 98 DelayedCallManager DelayedCallManager 68 | 102 TextMesh TextMesh 69 | 104 RenderSettings RenderSettings 70 | 108 Light Light 71 | 109 CGProgram CGProgram 72 | 110 BaseAnimationTrack BaseAnimationTrack 73 | 111 Animation Animation 74 | 114 MonoBehaviour MonoBehaviour 75 | 115 MonoScript MonoScript 76 | 116 MonoManager MonoManager 77 | 117 Texture3D tex 78 | 118 NewAnimationTrack NewAnimationTrack 79 | 119 Projector Projector 80 | 120 LineRenderer LineRenderer 81 | 121 Flare Flare 82 | 122 Halo Halo 83 | 123 LensFlare LensFlare 84 | 124 FlareLayer FlareLayer 85 | 125 HaloLayer HaloLayer 86 | 126 NavMeshAreas NavMeshAreas 87 | 127 HaloManager HaloManager 88 | 128 Font Font 89 | 129 PlayerSettings PlayerSettings 90 | 130 NamedObject NamedObject 91 | 131 GUITexture tex 92 | 132 GUIText GUIText 93 | 133 GUIElement GUIElement 94 | 134 PhysicMaterial PhysicMaterial 95 | 135 SphereCollider SphereCollider 96 | 136 CapsuleCollider CapsuleCollider 97 | 137 SkinnedMeshRenderer SkinnedMeshRenderer 98 | 138 FixedJoint FixedJoint 99 | 140 RaycastCollider RaycastCollider 100 | 141 BuildSettings BuildSettings 101 | 142 AssetBundle AssetBundle 102 | 143 CharacterController CharacterController 103 | 144 CharacterJoint CharacterJoint 104 | 145 SpringJoint SpringJoint 105 | 146 WheelCollider WheelCollider 106 | 147 ResourceManager ResourceManager 107 | 148 NetworkView NetworkView 108 | 149 NetworkManager NetworkManager 109 | 150 PreloadData PreloadData 110 | 152 MovieTexture MovieTexture 111 | 153 ConfigurableJoint ConfigurableJoint 112 | 154 TerrainCollider TerrainCollider 113 | 155 MasterServerInterface MasterServerInterface 114 | 156 TerrainData TerrainData 115 | 157 LightmapSettings LightmapSettings 116 | 158 WebCamTexture WebCamTexture 117 | 159 EditorSettings EditorSettings 118 | 160 InteractiveCloth InteractiveCloth 119 | 161 ClothRenderer ClothRenderer 120 | 162 EditorUserSettings EditorUserSettings 121 | 163 SkinnedCloth SkinnedCloth 122 | 164 AudioReverbFilter AudioReverbFilter 123 | 165 AudioHighPassFilter AudioHighPassFilter 124 | 166 AudioChorusFilter AudioChorusFilter 125 | 167 AudioReverbZone AudioReverbZone 126 | 168 AudioEchoFilter AudioEchoFilter 127 | 169 AudioLowPassFilter AudioLowPassFilter 128 | 170 AudioDistortionFilter AudioDistortionFilter 129 | 171 SparseTexture SparseTexture 130 | 180 AudioBehaviour AudioBehaviour 131 | 181 AudioFilter AudioFilter 132 | 182 WindZone WindZone 133 | 183 Cloth Cloth 134 | 184 SubstanceArchive SubstanceArchive 135 | 185 ProceduralMaterial ProceduralMaterial 136 | 186 ProceduralTexture ProceduralTexture 137 | 191 OffMeshLink OffMeshLink 138 | 192 OcclusionArea OcclusionArea 139 | 193 Tree Tree 140 | 194 NavMeshObsolete NavMeshObsolete 141 | 195 NavMeshAgent NavMeshAgent 142 | 196 NavMeshSettings NavMeshSettings 143 | 197 LightProbesLegacy LightProbesLegacy 144 | 198 ParticleSystem ParticleSystem 145 | 199 ParticleSystemRenderer ParticleSystemRenderer 146 | 200 ShaderVariantCollection ShaderVariantCollection 147 | 205 LODGroup LODGroup 148 | 206 BlendTree BlendTree 149 | 207 Motion Motion 150 | 208 NavMeshObstacle NavMeshObstacle 151 | 210 TerrainInstance TerrainInstance 152 | 212 SpriteRenderer SpriteRenderer 153 | 213 Sprite Sprite 154 | 214 CachedSpriteAtlas CachedSpriteAtlas 155 | 215 ReflectionProbe ReflectionProbe 156 | 216 ReflectionProbes ReflectionProbes 157 | 218 Terrain Terrain 158 | 220 LightProbeGroup LightProbeGroup 159 | 221 AnimatorOverrideController AnimatorOverrideController 160 | 222 CanvasRenderer CanvasRenderer 161 | 223 Canvas Canvas 162 | 224 RectTransform RectTransform 163 | 225 CanvasGroup CanvasGroup 164 | 226 BillboardAsset BillboardAsset 165 | 227 BillboardRenderer BillboardRenderer 166 | 228 SpeedTreeWindAsset SpeedTreeWindAsset 167 | 229 AnchoredJoint2D AnchoredJoint2D 168 | 230 Joint2D Joint2D 169 | 231 SpringJoint2D SpringJoint2D 170 | 232 DistanceJoint2D DistanceJoint2D 171 | 233 HingeJoint2D HingeJoint2D 172 | 234 SliderJoint2D SliderJoint2D 173 | 235 WheelJoint2D WheelJoint2D 174 | 238 NavMeshData NavMeshData 175 | 240 AudioMixer AudioMixer 176 | 241 AudioMixerController AudioMixerController 177 | 243 AudioMixerGroupController AudioMixerGroupController 178 | 244 AudioMixerEffectController AudioMixerEffectController 179 | 245 AudioMixerSnapshotController AudioMixerSnapshotController 180 | 246 PhysicsUpdateBehaviour2D PhysicsUpdateBehaviour2D 181 | 247 ConstantForce2D ConstantForce2D 182 | 248 Effector2D Effector2D 183 | 249 AreaEffector2D AreaEffector2D 184 | 250 PointEffector2D PointEffector2D 185 | 251 PlatformEffector2D PlatformEffector2D 186 | 252 SurfaceEffector2D SurfaceEffector2D 187 | 258 LightProbes LightProbes 188 | 271 SampleClip SampleClip 189 | 272 AudioMixerSnapshot AudioMixerSnapshot 190 | 273 AudioMixerGroup AudioMixerGroup 191 | 290 AssetBundleManifest AssetBundleManifest 192 | 1001 Prefab Prefab 193 | 1002 EditorExtensionImpl EditorExtensionImpl 194 | 1003 AssetImporter AssetImporter 195 | 1004 AssetDatabase AssetDatabase 196 | 1005 Mesh3DSImporter Mesh3DSImporter 197 | 1006 TextureImporter TextureImporter 198 | 1007 ShaderImporter ShaderImporter 199 | 1008 ComputeShaderImporter ComputeShaderImporter 200 | 1011 AvatarMask AvatarMask 201 | 1020 AudioImporter AudioImporter 202 | 1026 HierarchyState HierarchyState 203 | 1027 GUIDSerializer GUIDSerializer 204 | 1028 AssetMetaData AssetMetaData 205 | 1029 DefaultAsset DefaultAsset 206 | 1030 DefaultImporter DefaultImporter 207 | 1031 TextScriptImporter TextScriptImporter 208 | 1032 SceneAsset SceneAsset 209 | 1034 NativeFormatImporter NativeFormatImporter 210 | 1035 MonoImporter MonoImporter 211 | 1037 AssetServerCache AssetServerCache 212 | 1038 LibraryAssetImporter LibraryAssetImporter 213 | 1040 ModelImporter ModelImporter 214 | 1041 FBXImporter FBXImporter 215 | 1042 TrueTypeFontImporter TrueTypeFontImporter 216 | 1044 MovieImporter MovieImporter 217 | 1045 EditorBuildSettings EditorBuildSettings 218 | 1046 DDSImporter DDSImporter 219 | 1048 InspectorExpandedState InspectorExpandedState 220 | 1049 AnnotationManager AnnotationManager 221 | 1050 PluginImporter PluginImporter 222 | 1051 EditorUserBuildSettings EditorUserBuildSettings 223 | 1052 PVRImporter PVRImporter 224 | 1053 ASTCImporter ASTCImporter 225 | 1054 KTXImporter KTXImporter 226 | 1101 AnimatorStateTransition AnimatorStateTransition 227 | 1102 AnimatorState AnimatorState 228 | 1105 HumanTemplate HumanTemplate 229 | 1107 AnimatorStateMachine AnimatorStateMachine 230 | 1108 PreviewAssetType PreviewAssetType 231 | 1109 AnimatorTransition AnimatorTransition 232 | 1110 SpeedTreeImporter SpeedTreeImporter 233 | 1111 AnimatorTransitionBase AnimatorTransitionBase 234 | 1112 SubstanceImporter SubstanceImporter 235 | 1113 LightmapParameters LightmapParameters 236 | 1120 LightmapSnapshot LightmapSnapshot 237 | -------------------------------------------------------------------------------- /LibUnity.py: -------------------------------------------------------------------------------- 1 | # /usr/bin/python2 2 | # -*- coding:utf-8 -*- 3 | ''' 4 | 2017-05-18 Unity Assets Reader完全重构 5 | @author: wmltogether 6 | ''' 7 | import os 8 | import struct 9 | import md5 10 | import BinaryHelper 11 | 12 | 13 | class ClassID: 14 | 15 | def __init__(self, major_ver, minor_ver): 16 | self.classDict = {} 17 | self.version = major_ver * 100 + minor_ver 18 | self._set() 19 | 20 | def _set(self): 21 | fs = open("classidFormat.pydat", "rb") 22 | lines = fs.readlines() 23 | for line in lines: 24 | if (len(line) > 1) and ("\t" in line): 25 | line = line.replace("\r", "") 26 | line = line.replace("\n", "") 27 | classid = int(line.split("\t")[0], 10) 28 | classtype = line.split("\t")[1] 29 | extension_name = "." + line.split("\t")[2] 30 | self.classDict[classid] = (classtype, extension_name) 31 | 32 | def GetClassDict(self): 33 | return self.classDict 34 | 35 | 36 | class Uitlity(object): 37 | 38 | def __init__(self): 39 | 40 | pass 41 | 42 | @staticmethod 43 | def GetObbMD5(obbName): 44 | with open(obbName, "rb") as fs: 45 | fs.seek(-0x10016, 2) 46 | string = fs.read(0x10016) 47 | m = md5.new() 48 | 49 | m.update(string) 50 | return m.hexdigest() 51 | 52 | @staticmethod 53 | def GetFileSize(fName): 54 | return os.path.getsize(fName) 55 | 56 | 57 | class AssetsLoader(object): 58 | UnityRuntimePlatform = {4: "Mac OS X", 59 | 5: "PC", 60 | 6: "Web Player", 61 | 7: "Web Stream", 62 | 9: "iOS", 63 | 10: "PS3", 64 | 11: "Xbox360", 65 | 13: "Android" 66 | } 67 | 68 | def __init__(self, assetName): 69 | self.packageName = assetName 70 | self.packageSize = os.path.getsize(assetName) 71 | self.packageMajorVersion = 0 72 | self.packageMinorVersion = 0 73 | self.packagePatchVersion = "" 74 | self.packageItemNums = 0 75 | 76 | self.packageGen = 0 # 包版本号 77 | self.headerSize = 0 78 | self._tableSize = 0 79 | self.ObjectsEntryOffset = 0 80 | self.IndexEntryOffset = 0 81 | self.platformType = 0 82 | self.baseStream = None 83 | self.EntryList = [] 84 | self.ClassIDDict = {} 85 | self.LinkedClassIDDict = {} # unity 5.5专用 86 | self.Load() 87 | 88 | print( 89 | "**********************\n" + 90 | " Unity Package: \t%s\n" % self.packageName + 91 | " PackageVersion: \t%d\n" % self.packageGen + 92 | " Unity Version: \t%d.%d.%s\n" % (self.packageMajorVersion, 93 | self.packageMinorVersion, 94 | self.packagePatchVersion) + 95 | " Platform: \t%d\n" % self.platformType + 96 | " Object nums: \t%d\n" % self.packageItemNums + 97 | "**********************\n") 98 | 99 | def Load(self): 100 | self.baseStream = open(self.packageName, "rb+") 101 | br = BinaryHelper.BinaryReader(self.baseStream) 102 | self._tableSize = br.ReadBEUInt32() 103 | self.packageSize = br.ReadBEUInt32() 104 | self.packageGen = br.ReadBEUInt32() 105 | self.ObjectsEntryOffset = br.ReadBEUInt32() 106 | 107 | self.headerSize = self.ObjectsEntryOffset 108 | if (self.packageGen == 9): # unity 3.5 - 4.6 109 | br.Seek(4, 1) 110 | version = br.ReadCString() 111 | br.Seek(1, 1) 112 | self.platformType = br.ReadUInt32() 113 | self._checkVersion(version) 114 | br.Seek(8, 1) 115 | self.IndexEntryOffset = br.Tell() 116 | self.ClassIDDict = ClassID( 117 | self.packageMajorVersion, 118 | self.packageMinorVersion).GetClassDict() 119 | 120 | self._getPackageIndex35() 121 | 122 | elif (self.packageGen == 15): # unity 5.0.1 - 5.4 123 | br.Seek(4, 1) 124 | version = br.ReadCString() 125 | br.Seek(1, 1) 126 | self.platformType = br.ReadUInt32() 127 | self._checkVersion(version) 128 | unkBool = ord(br.ReadByte()) 129 | base_nums = br.ReadUInt32() 130 | for i in xrange(base_nums): 131 | m0 = br.ReadInt32() 132 | if (m0 < 0): 133 | br.Seek(0x20, 1) 134 | else: 135 | br.Seek(0x10, 1) 136 | self.IndexEntryOffset = br.Tell() 137 | self.ClassIDDict = ClassID( 138 | self.packageMajorVersion, 139 | self.packageMinorVersion).GetClassDict() 140 | 141 | self._getPackageIndex50() 142 | 143 | elif (self.packageGen == 17): # unity 5.5.0 144 | br.Seek(4, 1) 145 | version = br.ReadCString() 146 | br.Seek(1, 1) 147 | self.platformType = br.ReadUInt32() 148 | self._checkVersion(version) 149 | unkBool = ord(br.ReadByte()) 150 | base_nums = br.ReadUInt32() 151 | print("base_nums:%x" % base_nums) 152 | 153 | for i in xrange(base_nums): 154 | m0 = br.ReadInt32() 155 | m1 = ord(br.ReadByte()) 156 | m2 = br.ReadInt16() 157 | chk = m2 158 | if (m2 >= 0): 159 | chk = -1 - m2 160 | else: 161 | chk = m0 162 | self.LinkedClassIDDict[i] = m0 163 | tmp = br.ReadInt32() 164 | if (tmp == 0): 165 | br.Seek(0x10, 1) 166 | br.Seek(-4, 1) 167 | if (chk < 0): 168 | br.Seek(0x10, 1) 169 | br.Seek(0x10, 1) 170 | self.ClassIDDict = ClassID( 171 | self.packageMajorVersion, 172 | self.packageMinorVersion).GetClassDict() 173 | self.IndexEntryOffset = br.Tell() 174 | self._getPackageIndex55() 175 | 176 | pass 177 | 178 | def _getPackageIndex35(self): 179 | # 3.5.x - 4.6.x的打包格式 180 | br = BinaryHelper.BinaryReader(self.baseStream) 181 | br.Seek(self.IndexEntryOffset) 182 | self.packageItemNums = br.ReadInt32() 183 | position = br.Tell() 184 | for i in xrange(self.packageItemNums): 185 | br.Seek(position, 0) 186 | m_pathID = br.ReadInt32() 187 | t_pos = br.Tell() # 记录offset位置 188 | (offset, size) = struct.unpack( 189 | "2I", self.baseStream.read(0x8)) 190 | classid0 = br.ReadInt32() 191 | classid = br.ReadInt32() 192 | 193 | position = br.Tell() 194 | 195 | # 记录到entry class 196 | index_entry = self.IndexEntry() 197 | index_entry.IndexID = i 198 | index_entry.Offset = offset + self.headerSize 199 | index_entry.Size = size 200 | index_entry.ClassID = classid 201 | index_entry.Offset_ptr = t_pos 202 | 203 | br.Seek(offset + self.headerSize, 0) 204 | # 分析可能的文件名和文件类型 205 | fname = "" 206 | if (index_entry.Size > 4): 207 | fname_length = br.ReadInt32() 208 | if (0 < fname_length < 0x20): 209 | fname = self._fixAsciiName(br.ReadBytes(fname_length)) 210 | ext_name = ".bin" 211 | if (index_entry.ClassID in self.ClassIDDict): 212 | ext_name = self.ClassIDDict[index_entry.ClassID][1] 213 | index_entry.ObjectName = fname + ext_name 214 | 215 | # 存储到list 216 | self.EntryList.append(index_entry) 217 | pass 218 | 219 | def _getPackageIndex50(self): 220 | # 5.0.1 - 5.4.x 的打包格式 221 | br = BinaryHelper.BinaryReader(self.baseStream) 222 | br.Seek(self.IndexEntryOffset) 223 | self.packageItemNums = br.ReadInt32() 224 | position = br.Tell() 225 | for i in xrange(self.packageItemNums): 226 | # 每个entry都要对齐到4字节 227 | br.Seek(position, 0) 228 | if br.Tell() % 4 != 0: 229 | br.Seek(4 - br.Tell() % 4, 1) 230 | m_pathID = br.ReadInt64() 231 | t_pos = br.Tell() # 记录offset位置 232 | (offset, size) = struct.unpack( 233 | "2I", self.baseStream.read(0x8)) 234 | classid = br.ReadInt32() 235 | 236 | classid2 = br.ReadInt16() 237 | unk = br.ReadInt16() 238 | unkByte = br.ReadByte() 239 | 240 | position = br.Tell() 241 | # 记录到entry class 242 | index_entry = self.IndexEntry() 243 | index_entry.IndexID = i 244 | index_entry.Offset = offset + self.headerSize 245 | index_entry.Size = size 246 | index_entry.ClassID = classid 247 | index_entry.Offset_ptr = t_pos 248 | 249 | br.Seek(offset + self.headerSize, 0) 250 | # 分析可能的文件名和文件类型 251 | fname = "" 252 | if (index_entry.Size > 4): 253 | fname_length = br.ReadInt32() 254 | if (0 < fname_length < 0x20): 255 | fname = self._fixAsciiName(br.ReadBytes(fname_length)) 256 | ext_name = ".bin" 257 | if (index_entry.ClassID in self.ClassIDDict): 258 | ext_name = self.ClassIDDict[index_entry.ClassID][1] 259 | index_entry.ObjectName = fname + ext_name 260 | 261 | # 存储到list 262 | self.EntryList.append(index_entry) 263 | 264 | pass 265 | 266 | def _getPackageIndex55(self): 267 | # 5.5.x 268 | print("Index Entry:%08x" % self.IndexEntryOffset) 269 | br = BinaryHelper.BinaryReader(self.baseStream) 270 | br.Seek(self.IndexEntryOffset) 271 | 272 | self.packageItemNums = br.ReadInt32() # 实际是读4字节,后面再按照4字节补齐 273 | position = br.Tell() 274 | for i in xrange(self.packageItemNums): 275 | br.Seek(position, 0) 276 | if br.Tell() % 4 != 0: 277 | br.Seek(4 - br.Tell() % 4, 1) 278 | (index_id, unk) = struct.unpack("2I", self.baseStream.read(0x8)) 279 | t_pos = br.Tell() 280 | 281 | (offset, size, classid) = struct.unpack( 282 | "3I", self.baseStream.read(0xc)) 283 | if (classid in self.LinkedClassIDDict): 284 | classid = self.LinkedClassIDDict[classid] 285 | #print("%08x"%(offset + self.headerSize)) 286 | position = br.Tell() 287 | index_entry = self.IndexEntry() 288 | index_entry.IndexID = i 289 | index_entry.Offset = offset + self.headerSize 290 | index_entry.Size = size 291 | index_entry.ClassID = classid 292 | index_entry.Offset_ptr = t_pos 293 | 294 | br.Seek(offset + self.headerSize, 0) 295 | 296 | fname = "" 297 | if (index_entry.Size > 4): 298 | fname_length = br.ReadInt32() 299 | if (0 < fname_length < 0x20): 300 | fname = self._fixAsciiName(br.ReadBytes(fname_length)) 301 | ext_name = ".bin" 302 | if (index_entry.ClassID in self.ClassIDDict): 303 | ext_name = self.ClassIDDict[index_entry.ClassID][1] 304 | index_entry.ObjectName = fname + ext_name 305 | print("Entry %d %s :%08x,%08x,Class ID :%08x" % (index_entry.IndexID, 306 | index_entry.ObjectName, index_entry.Offset, index_entry.Size, classid)) 307 | self.EntryList.append(index_entry) 308 | 309 | pass 310 | 311 | def _checkVersion(self, version_string): 312 | (a, b, c) = version_string.split(".")[:3] 313 | self.packageMajorVersion = int(a) 314 | self.packageMinorVersion = int(b) 315 | self.packagePatchVersion = c 316 | pass 317 | 318 | def _fixAsciiName(self, name): 319 | result = "" 320 | for var in name: 321 | if 0x20 <= ord(var) <= 0x7e: 322 | if (ord(var) == 0x2f): 323 | var = "_" 324 | result += var 325 | pass 326 | else: 327 | return "" 328 | return result 329 | 330 | # 解包到目录 331 | 332 | def Unpack(self, dst_folder): 333 | print("Unpacking Assets...") 334 | br = BinaryHelper.BinaryReader(self.baseStream) 335 | if not os.path.exists(dst_folder): 336 | os.makedirs(dst_folder) 337 | 338 | for entry in self.EntryList: 339 | br.Seek(entry.Offset) 340 | data = br.ReadBytes(entry.Size) 341 | with open("%s/%08d_%s" % (dst_folder, 342 | entry.IndexID, 343 | entry.ObjectName), "wb") as dst: 344 | dst.write(data) 345 | self.baseStream.close() 346 | print("Assets Unpacked") 347 | pass 348 | 349 | # 从文件夹打包到assets 350 | 351 | def Pack(self, input_folder): 352 | print("Packing Assets...") 353 | br = BinaryHelper.BinaryReader(self.baseStream) 354 | br.Seek(self.ObjectsEntryOffset, 0) 355 | 356 | # 检查所有文件是否存在 357 | for entry in self.EntryList: 358 | name = "%s/%08d_%s" % (input_folder, 359 | entry.IndexID, 360 | entry.ObjectName) 361 | if not os.path.exists(name): 362 | print("Error: asset not found:%s" % name) 363 | return False 364 | 365 | self.baseStream.truncate() 366 | 367 | for i in xrange(len(self.EntryList)): 368 | entry = self.EntryList[i] 369 | name = "%s/%08d_%s" % (input_folder, 370 | entry.IndexID, 371 | entry.ObjectName) 372 | fs = open(name, "rb") 373 | data = fs.read() 374 | length = len(data) 375 | fs.close() 376 | pos = self.baseStream.tell() 377 | if pos % 8 != 0: 378 | self.baseStream.write("\x00" * (8 - pos % 8)) 379 | pos = self.baseStream.tell() 380 | 381 | self.baseStream.write(data) 382 | # 更新entry 383 | entry.Offset = pos 384 | entry.Size = length 385 | self.EntryList[i] = entry 386 | 387 | self.baseStream.seek(0, 2) 388 | total_size = self.baseStream.tell() 389 | 390 | for i in xrange(len(self.EntryList)): 391 | entry = self.EntryList[i] 392 | self.baseStream.seek(entry.Offset_ptr) 393 | self.baseStream.write(struct.pack( 394 | "I", entry.Offset - self.headerSize)) 395 | self.baseStream.write(struct.pack("I", entry.Size)) 396 | self.baseStream.seek(4, 0) 397 | self.baseStream.write(struct.pack(">I", total_size)) 398 | self.baseStream.close() 399 | pass 400 | 401 | def Close(self): 402 | if self.baseStream is not None: 403 | self.baseStream.close() 404 | 405 | class IndexEntry: 406 | Offset_ptr = 0 407 | Offset = 0 408 | Size = 0 409 | IndexID = 0 410 | ClassID = 0 411 | ObjectName = "" 412 | 413 | 414 | def test(): 415 | loader = AssetsLoader("sharedassets0.assets") 416 | loader.Unpack("sharedassets0.assets_unpacked") 417 | --------------------------------------------------------------------------------