├── .gitignore ├── README.md ├── cli.js ├── index.js ├── lib ├── ConvertMesh.js ├── ConvertTexture2D.js ├── ExtractBone.js └── UnpackETC1.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | yarn.lock 3 | 4 | *.unity3d 5 | *.unity3d.lz4 6 | *.obj 7 | *.png 8 | *_bone.json 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity Unpacker 2 | 3 | Highly experimental Unity unpacker in Node.js. 4 | 5 | This is not an universal unity unpacker, but only targeting Unity 5.1.2f. I may eventually add supporting code for other versions, but for now it only supports 5.1.2f version. 6 | 7 | Work in progress. 8 | 9 | ## Installation 10 | 11 | ``` 12 | $ npm install unity-unpacker -g 13 | $ # or 14 | $ yarn global add unity-unpacker 15 | ``` 16 | 17 | ## Usage 18 | 19 | ``` 20 | Usage: unity-unpacker [options] <.unity3d or .unity3d.lz4> 21 | 22 | Options: 23 | --output-dir, -o Unpacked files' destination directory 24 | If not specified, new directory will be created on current 25 | working directory, and set to the directory 26 | --dry-run, -D Files won't be really extracted, but just parsed and 27 | informations will be printed [boolean] [default: false] 28 | --verbose, -v Detailed information about assets will be printed 29 | [boolean] [default: false] 30 | --image Textures' output format 31 | [choices: "jpg", "png"] [default: "png"] 32 | --help Show help [boolean] 33 | ``` 34 | 35 | ## Reference 36 | 37 | * https://github.com/HearthSim/UnityPack (MIT License) 38 | * https://github.com/RaduMC/UnityStudio (MIT License) 39 | * https://www.khronos.org/registry/gles/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt 40 | 41 | ## Thanks to 42 | 43 | * @marcan - Fixed ETC1 unpacker 44 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | "use strict"; 4 | 5 | const fs = require("fs"); 6 | const path = require("path"); 7 | 8 | const yargs = require("yargs"); 9 | 10 | const unpack = require("./index"); 11 | 12 | //============================================================================== 13 | 14 | let options = yargs 15 | .usage("Usage: $0 [options] <.unity3d or .unity3d.lz4>") 16 | .option("output-dir", { 17 | alias: "o", 18 | describe: "Unpacked files' destination directory\nIf not specified, new directory will be created on current working directory, and set to the directory", 19 | }) 20 | .option("dry-run", { 21 | alias: "D", 22 | describe: "Files won't be really extracted, but just parsed and informations will be printed", 23 | boolean: true, 24 | default: false, 25 | }) 26 | .option("verbose", { 27 | alias: "v", 28 | describe: "Detailed information about assets will be printed", 29 | boolean: true, 30 | default: false, 31 | }) 32 | .option("image", { 33 | describe: "Textures' output format", 34 | choices: [ "jpg", "png" ], 35 | default: "png", 36 | }) 37 | .help() 38 | .demand(1) // file 39 | .argv; 40 | 41 | //============================================================================== 42 | 43 | let inFile = options._[0]; 44 | let inBuf = fs.readFileSync(inFile); 45 | 46 | options.lz4 = inFile.endsWith(".unity3d.lz4"); 47 | 48 | let result = unpack(inBuf, options); 49 | 50 | if(options.dryRun) process.exit(0); 51 | 52 | if(!options.outputDir) { 53 | let dir = path.parse(inFile).base.replace(/\.unity3d(?:\.lz4)?$/, ""); 54 | fs.mkdirSync(dir); 55 | options.outputDir = path.join(process.cwd(), dir); 56 | } 57 | 58 | for(let file of result) { 59 | let filename = path.join(options.outputDir, file.filename); 60 | 61 | switch(file.type) { 62 | case "mesh": 63 | fs.writeFileSync(filename, file.objData); 64 | break; 65 | case "texture": 66 | file.image.write(filename); 67 | break; 68 | case "bone": 69 | fs.writeFileSync(filename, JSON.stringify(file.data)); 70 | break; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Unity = require("unity-parser"); 4 | 5 | const ConvertTexture2D = require("./lib/ConvertTexture2D"); 6 | const ConvertMesh = require("./lib/ConvertMesh"); 7 | const ExtractBone = require("./lib/ExtractBone"); 8 | 9 | //============================================================================== 10 | 11 | function unpack(inBuf, opt = {}) { 12 | let options = Object.assign({ 13 | dryRun: false, 14 | verbose: false, 15 | image: "png", 16 | lz4: false, 17 | }, opt); 18 | 19 | const LOG = msg => { if(options.verbose) console.log(msg); }; 20 | 21 | let unityBundle; 22 | try { 23 | unityBundle = Unity.load(inBuf, options.lz4); 24 | } catch(e) { 25 | console.log(e); 26 | throw "Error while reading .unity3d file"; 27 | } 28 | 29 | LOG("[ ] .unity3d file has loaded and parsed"); 30 | 31 | let data = []; 32 | 33 | for(let i = 0 ; i < unityBundle.length ; i++) { 34 | let unityAsset = unityBundle[i]; 35 | 36 | let suffix = ""; 37 | if(1 < unityBundle.length) suffix += i + "_"; 38 | 39 | for(let pathId in unityAsset.data) { 40 | let object = unityAsset.data[pathId]; 41 | 42 | LOG(`[ ] ${pathId} - ${object._type}`); 43 | 44 | if(object._type === "Mesh") { 45 | LOG(`[+] Extracting Mesh as OBJ format`); 46 | 47 | let filename = suffix + object.m_Name + ".obj"; 48 | let obj = ConvertMesh(object); 49 | 50 | data.push({ type: "mesh", filename, objData: obj }); 51 | } 52 | else if(object._type === "Texture2D") { 53 | LOG(`[+] Extracting Texture2D`); 54 | 55 | let filename = suffix + object.m_Name + "." + options.image; 56 | let img = ConvertTexture2D(object); 57 | 58 | data.push({ type: "texture", filename, image: img }); 59 | } 60 | else if(object._type === "SkinnedMeshRenderer") { 61 | LOG(`[+] Extracting bones as JSON format`); 62 | 63 | let filename = suffix + "bone.json"; 64 | let bone = ExtractBone(unityAsset); 65 | 66 | data.push({ type: "bone", filename, data: bone }); 67 | } 68 | } 69 | } 70 | 71 | return data; 72 | } 73 | 74 | module.exports = unpack; 75 | 76 | -------------------------------------------------------------------------------- /lib/ConvertMesh.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function read2Float(buf, pos) { 4 | return { 5 | x: buf.readFloatLE(pos), 6 | y: buf.readFloatLE(pos + 4), 7 | toString() { return `${this.x} ${1 - this.y}`; } 8 | }; 9 | } 10 | 11 | function read3Float(buf, pos) { 12 | return { 13 | x: buf.readFloatLE(pos), 14 | y: buf.readFloatLE(pos + 4), 15 | z: buf.readFloatLE(pos + 8), 16 | toString() { return `${-this.x} ${this.y} ${this.z}`; } 17 | }; 18 | } 19 | 20 | function read4Float(buf, pos) { 21 | return { 22 | x: buf.readFloatLE(pos), 23 | y: buf.readFloatLE(pos + 4), 24 | z: buf.readFloatLE(pos + 8), 25 | w: buf.readFloatLE(pos + 12), 26 | toString() { return `${this.x} ${this.y} ${this.z} ${this.w}`; } 27 | }; 28 | } 29 | 30 | function read4UInt8(buf, pos) { 31 | return { 32 | x: buf.readUInt8(pos), 33 | y: buf.readUInt8(pos + 1), 34 | z: buf.readUInt8(pos + 2), 35 | w: buf.readUInt8(pos + 3), 36 | toString() { return `${this.x} ${this.y} ${this.z} ${this.w}`; } 37 | }; 38 | } 39 | 40 | //============================================================================== 41 | 42 | function getMeshData(mesh) { 43 | let indices = []; 44 | let triangles = []; 45 | let vertices = []; 46 | let normals = []; 47 | let colors = []; 48 | let uv1 = []; 49 | let uv2 = []; 50 | let uv3 = []; 51 | let uv4 = []; 52 | let tangents = []; 53 | 54 | for(let submesh of mesh.m_SubMeshes.Array) { 55 | let subIndices = []; 56 | let subTriangles = []; 57 | 58 | let buf = mesh.m_IndexBuffer.Array, pos = submesh.firstByte; 59 | 60 | for(let i = 0 ; i < submesh.indexCount ; i++) { 61 | subIndices.push(buf.readUInt16LE(pos)); 62 | pos += 2; 63 | } 64 | 65 | if(submesh.topology !== 0) 66 | throw `Not supported topology ${submesh.topology}`; 67 | 68 | subTriangles = subTriangles.concat(subIndices); 69 | 70 | indices.push(subIndices); 71 | triangles.push(subTriangles); 72 | } 73 | 74 | let buf = mesh.m_VertexData.m_DataSize, pos = 0; 75 | let channels = mesh.m_VertexData.m_Channels.Array; 76 | let streams = channels.map(c => c.stream); 77 | let streamCount = streams.filter((v, i, a) => a.indexOf(v) === i).length; 78 | let channelCount = channels.length; 79 | 80 | let streamSize = new Array(streamCount).fill(0); 81 | 82 | for(let ch of channels) { 83 | if(ch.dimension <= 0) continue; 84 | 85 | if(ch.format === 1) throw "16 bit floats are not supported"; 86 | 87 | switch(ch.format) { 88 | case 0: streamSize[ch.stream] += ch.dimension * 4; break; 89 | case 1: throw "16 bit floats are not supported"; 90 | case 2: streamSize[ch.stream] += ch.dimension * 1; break; 91 | default: throw "unknown format"; 92 | } 93 | } 94 | 95 | let vertexCount = mesh.m_VertexData.m_VertexCount; 96 | 97 | for(let s = 0 ; s < streamCount ; s++) { 98 | for(let i = 0 ; i < vertexCount ; i++) { 99 | for(let j = 0 ; j < channelCount ; j++) { 100 | let ch; 101 | if(0 < channelCount) { 102 | ch = channels[j]; 103 | if(ch.format === 1) throw "16 bit floats are not supported"; 104 | } 105 | 106 | if(ch && 0 < ch.dimension && ch.stream === s) { 107 | switch(j) { 108 | case 0: vertices.push(read3Float(buf, pos)); pos += 12; break; 109 | case 1: normals.push(read3Float(buf, pos)); pos += 12; break; 110 | case 2: colors.push(read4UInt8(buf, pos)); pos += 4; break; 111 | case 3: uv1.push(read2Float(buf, pos)); pos += 8; break; 112 | case 4: uv2.push(read2Float(buf, pos)); pos += 8; break; 113 | case 5: uv3.push(read2Float(buf, pos)); pos += 8; break; 114 | case 6: uv3.push(read2Float(buf, pos)); pos += 8; break; 115 | case 7: tangents.push(read4Float(buf, pos)); pos += 16; break; 116 | } 117 | } 118 | } 119 | } 120 | 121 | // Quote from reference 122 | // > sometimes there are 8 bytes between streams 123 | // > this is NOT an alignment, even if sometimes it may seem so 124 | // Ref: RaduMC/UnityStudio/Unity Studio/Unity Classes/Mesh.cs#L666-L667 125 | if(streamCount === 2 && s === 0) 126 | pos = buf.length - streamSize[1] * vertexCount; 127 | } 128 | 129 | return { 130 | indices, triangles, 131 | vertices, normals, colors, 132 | uv1, uv2, uv3, uv4, 133 | tangents, 134 | }; 135 | } 136 | 137 | //============================================================================== 138 | 139 | function convertMesh(mesh) { 140 | let meshData = getMeshData(mesh); 141 | 142 | let ret = []; 143 | let verticalsPerFace = 3; 144 | 145 | let normals = meshData.normals; 146 | let texCoords = meshData.uv1; 147 | if(!texCoords) texCoords = meshData.uv2; 148 | 149 | for(let v of meshData.vertices) 150 | ret.push(`v ${v.toString()}\n`); 151 | for(let v of normals) 152 | ret.push(`vn ${v.toString()}\n`); 153 | for(let v of texCoords) 154 | ret.push(`vt ${v.toString()}\n`); 155 | 156 | let subCount = mesh.m_SubMeshes.Array.length; 157 | for(let i = 0 ; i < subCount ; i++) { 158 | if(subCount === 1) { 159 | ret.push(`g ${mesh.m_Name}\n`); 160 | ret.push(`usemtl ${mesh.m_Name}\n`); 161 | } 162 | else { 163 | ret.push(`g ${mesh.m_Name}_${i}\n`); 164 | ret.push(`usemtl ${mesh.m_Name}_${i}\n`); 165 | } 166 | 167 | ret.push(`s 1\n`); 168 | 169 | let faceTri = []; 170 | for(let t of meshData.triangles[i]) { 171 | faceTri.push(t); 172 | 173 | if(faceTri.length === verticalsPerFace) { 174 | let face = "f "; 175 | 176 | for(let i of faceTri.reverse()) { 177 | face += (i + 1).toString(); 178 | 179 | if(texCoords || normals) { 180 | face += "/"; 181 | 182 | if(texCoords) face += (i + 1).toString(); 183 | if(normals) face += "/" + (i + 1).toString(); 184 | } 185 | 186 | face += " "; 187 | } 188 | 189 | face += "\n"; 190 | ret.push(face); 191 | faceTri = []; 192 | } 193 | } 194 | 195 | ret.push("\n"); 196 | } 197 | 198 | return ret.join(""); 199 | } 200 | 201 | module.exports = convertMesh; 202 | -------------------------------------------------------------------------------- /lib/ConvertTexture2D.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Jimp = require("jimp"); 4 | const UnpackETC1 = require("./UnpackETC1"); 5 | 6 | //============================================================================== 7 | 8 | const A8 = 1; 9 | const A4_R4_G4_B4 = 2; 10 | const R8_G8_B8 = 3; 11 | const R8_G8_B8_A8 = 4; 12 | const A8_R8_G8_B8 = 5; 13 | const R5_G6_B5 = 7; 14 | const R4_G4_B4_A4 = 13; 15 | // const PVRTC_RGB2 = 30; // TODO 16 | // const PVRTC_RGBA2 = 31; // TODO 17 | // const PVRTC_RGB4 = 32; // TODO 18 | // const PVRTC_RGBA4 = 33; // TODO 19 | const ETC1_RGB = 34; 20 | 21 | const TEX_FORMAT_ALPHA = [ A8, A4_R4_G4_B4, R8_G8_B8_A8, A8_R8_G8_B8, R4_G4_B4_A4 ]; 22 | const TEX_FORMAT_NONALPHA = [ R8_G8_B8, R5_G6_B5, ETC1_RGB ]; 23 | const TEX_FORMAT = [].concat(TEX_FORMAT_NONALPHA, TEX_FORMAT_NONALPHA); 24 | 25 | //============================================================================== 26 | 27 | function convertTexture2D(texture) { 28 | let width = texture.m_Width; 29 | let height = texture.m_Height; 30 | let format = texture.m_TextureFormat; 31 | let data = texture["image data"]; 32 | 33 | let img = new Jimp(width, height); 34 | let x = 0, y = 0; 35 | 36 | if(TEX_FORMAT_NONALPHA.indexOf(format) >= 0) img.rgba(false); 37 | else if(TEX_FORMAT_ALPHA.indexOf(format) >= 0) img.rgba(true); 38 | else throw "Unsupported format " + format; 39 | 40 | //==================================== 41 | // Handle special formats first 42 | 43 | if(format === ETC1_RGB) { 44 | for(let o = 0 ; o < data.length ; o += 8) { 45 | let upper = data.readUInt32BE(o); 46 | let lower = data.readUInt32BE(o + 4); 47 | let block = UnpackETC1(upper, lower); 48 | 49 | for(let i = 0 ; i < 16 ; i++) { 50 | let dx = i % 4; 51 | let dy = ~~(i / 4); 52 | img.setPixelColor(Jimp.rgbaToInt(...block[i], 255), x + dx, y + dy); 53 | } 54 | 55 | if(width <= x + 4) { x = 0; y += 4; } 56 | else x += 4; 57 | } 58 | 59 | return img; 60 | } 61 | 62 | //==================================== 63 | // And the others 64 | 65 | for(let o = 0 ; o < data.length ; ) { 66 | let r = 0, g = 0, b = 0, a = 255; 67 | 68 | if(format === A8) { 69 | a = data.readUInt8(o); 70 | o += 1; 71 | } 72 | else if(format === A4_R4_G4_B4) { 73 | // aaaarrrr ggggbbbb 74 | let v = data.readUInt16LE(o); o += 2; 75 | b = ( v & 15) << 4; 76 | g = ((v >> 4) & 15) << 4; 77 | r = ((v >> 8) & 15) << 4; 78 | a = ((v >> 12) & 15) << 4; 79 | } 80 | else if(format === R8_G8_B8) { 81 | // rrrrrrrr gggggggg bbbbbbbb 82 | r = data.readUInt8(o); 83 | g = data.readUInt8(o+1); 84 | b = data.readUInt8(o+2); 85 | o += 3; 86 | } 87 | else if(format === R8_G8_B8_A8) { 88 | // rrrrrrrr gggggggg bbbbbbbb aaaaaaaa 89 | r = data.readUInt8(o); 90 | g = data.readUInt8(o+1); 91 | b = data.readUInt8(o+2); 92 | a = data.readUInt8(o+3); 93 | o += 4; 94 | } 95 | else if(format === A8_R8_G8_B8) { 96 | // aaaaaaaa rrrrrrrr gggggggg bbbbbbbb 97 | a = data.readUInt8(o); 98 | r = data.readUInt8(o+1); 99 | g = data.readUInt8(o+2); 100 | b = data.readUInt8(o+3); 101 | o += 4; 102 | } 103 | else if(format === R5_G6_B5) { 104 | // rrrrrggg gggbbbbb 105 | let v = data.readUInt16LE(o); o += 2; 106 | b = ( v & 31) << 3; 107 | g = ((v >> 5) & 63) << 2; 108 | r = ((v >> 11) & 31) << 3; 109 | } 110 | else if(format === R4_G4_B4_A4) { 111 | // rrrrgggg bbbbaaaa 112 | let v = data.readUInt16LE(o); o += 2; 113 | a = ( v & 15) << 4; 114 | b = ((v >> 4) & 15) << 4; 115 | g = ((v >> 8) & 15) << 4; 116 | r = ((v >> 12) & 15) << 4; 117 | } 118 | 119 | img.setPixelColor(Jimp.rgbaToInt(r, g, b, a), x, y); 120 | x++; 121 | if(x >= width) { x = 0; y++; } 122 | } 123 | 124 | return img; 125 | } 126 | 127 | module.exports = convertTexture2D; 128 | -------------------------------------------------------------------------------- /lib/ExtractBone.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function extractBone(unityAsset) { 4 | let transforms = {}; 5 | let transformName = {}; 6 | let rootPathId; 7 | 8 | for(let pathId in unityAsset.data) { 9 | let object = unityAsset.data[pathId]; 10 | 11 | if(object._type === "SkinnedMeshRenderer") { 12 | rootPathId = object.m_RootBone.m_PathID; 13 | } 14 | else if(object._type === "Transform") { 15 | transforms[pathId] = { 16 | rotation: object.m_LocalRotation, 17 | position: object.m_LocalPosition, 18 | scale: object.m_LocalScale, 19 | children: object.m_Children.Array.map(c => c.m_PathID), 20 | }; 21 | } 22 | else if(object._type === "GameObject") { 23 | let components = object.m_Component.Array.map(c => c.second.m_PathID); 24 | for(let id of components) { 25 | transformName[id] = object.m_Name; 26 | } 27 | } 28 | } 29 | 30 | //==================================== 31 | // Build bone structure 32 | 33 | function getBoneRecursive(pathId) { 34 | let bone = Object.assign({}, transforms[pathId]); 35 | bone.name = transformName[pathId]; 36 | bone.children = bone.children.map(getBoneRecursive); 37 | 38 | return bone; 39 | } 40 | 41 | let bone = getBoneRecursive(rootPathId); 42 | 43 | return bone; 44 | } 45 | 46 | module.exports = extractBone; 47 | -------------------------------------------------------------------------------- /lib/UnpackETC1.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const DIFFERENTIAL = [0, 1, 2, 3, -4, -3, -2, -1]; 4 | const TABLE = [ 5 | [ -8, -2, 2, 8 ], 6 | [ -17, -5, 5, 17 ], 7 | [ -29, -9, 9, 29 ], 8 | [ -42, -13, 13, 42 ], 9 | [ -60, -18, 18, 60 ], 10 | [ -80, -24, 24, 80 ], 11 | [ -106, -33, 33, 106 ], 12 | [ -183, -47, 47, 183 ], 13 | ]; 14 | const PIXEL_INDEX = [2, 3, 1, 0]; 15 | const SUBBLOCK = [ 16 | [ 0,0,1,1, 0,0,1,1, 0,0,1,1, 0,0,1,1 ], 17 | [ 0,0,0,0, 0,0,0,0, 1,1,1,1, 1,1,1,1 ], 18 | ] 19 | 20 | function clamp(i) { 21 | if(i < 0) return 0; 22 | if(255 < i) return 255; 23 | return i; 24 | } 25 | 26 | function unpackEtc1Block(upper, lower) { 27 | let flipbit = (upper ) & 1; 28 | let diffbit = (upper >> 1) & 1; 29 | let cw = [ (upper >> 5) & 7, (upper >> 2) & 7 ]; 30 | let baseColor = []; 31 | 32 | let r1, g1, b1, r2, g2, b2; 33 | 34 | if(diffbit === 0) { 35 | b2 = (upper >> 8) & 15; b2 = b2 << 4 | b2; 36 | b1 = (upper >> 12) & 15; b1 = b1 << 4 | b1; 37 | g2 = (upper >> 16) & 15; g2 = g2 << 4 | g2; 38 | g1 = (upper >> 20) & 15; g1 = g1 << 4 | g1; 39 | r2 = (upper >> 24) & 15; r2 = r2 << 4 | r2; 40 | r1 = (upper >> 28) & 15; r1 = r1 << 4 | r1; 41 | } 42 | else { 43 | let db = (upper >> 8) & 7; db = DIFFERENTIAL[db]; 44 | b1 = (upper >> 11) & 31; b2 = (b1 + db) & 31; 45 | let dg = (upper >> 16) & 7; dg = DIFFERENTIAL[dg]; 46 | g1 = (upper >> 19) & 31; g2 = (g1 + dg) & 31; 47 | let dr = (upper >> 24) & 7; dr = DIFFERENTIAL[dr]; 48 | r1 = (upper >> 27) & 31; r2 = (r1 + dr) & 31; 49 | 50 | r1 = r1 << 3 | (r1 >> 2); 51 | r2 = r2 << 3 | (r2 >> 2); 52 | g1 = g1 << 3 | (g1 >> 2); 53 | g2 = g2 << 3 | (g2 >> 2); 54 | b1 = b1 << 3 | (b1 >> 2); 55 | b2 = b2 << 3 | (b2 >> 2); 56 | } 57 | 58 | baseColor = [ [r1, g1, b1], [r2, g2, b2] ]; 59 | 60 | // 4x4 61 | let block = []; 62 | 63 | let high = (lower >> 16) & 0xffff; 64 | let low = lower & 0xffff; 65 | 66 | for(let i = 0 ; i < 16 ; i++) { 67 | let subblock = SUBBLOCK[flipbit][i]; 68 | let base = baseColor[subblock]; 69 | 70 | let bit = (i >> 2) | ((i & 3) << 2); 71 | let lsb = (low >> bit) & 1; 72 | let msb = (high >> bit) & 1; 73 | let index = PIXEL_INDEX[msb * 2 + lsb]; 74 | let diff = TABLE[cw[subblock]][index]; 75 | 76 | block[i] = [ base[0] + diff, base[1] + diff, base[2] + diff ]; 77 | block[i] = block[i].map(clamp); 78 | } 79 | 80 | return block; 81 | } 82 | 83 | module.exports = unpackEtc1Block; 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unity-unpacker", 3 | "version": "1.0.1", 4 | "description": "Highly Experimental Unity Unpacker", 5 | "main": "index.js", 6 | "repository": "https://github.com/Snack-X/unity-unpacker", 7 | "author": "Snack ", 8 | "license": "MIT", 9 | "dependencies": { 10 | "jimp": "^0.2.27", 11 | "unity-parser": "^1.1.1", 12 | "yargs": "^6.5.0" 13 | }, 14 | "bin": { 15 | "unity-unpacker": "./cli.js" 16 | } 17 | } 18 | --------------------------------------------------------------------------------