🎮
CS EDITOR
Create Your Avatar Bundle
Start Creating
🎮
CS EDITOR
Avatar Creator
←
💇
Hair
🎭
Mask
👕
Top
👖
Pant
HAIR
Select one item from each category to continue.
← Back
Complete
Processing…
Please wait
Select Size
Choose your game version
Free Fire
Free Fire MAX
// ============================================================ // Bundle combiner -- rebuilds the UnityFS bundle client-side // ============================================================ // Bundle combiner -- browser version. Rebuilds the UnityFS asset bundle by // splicing selected per-item "patches" into the original bundle's data area, // then re-compressing as a single LZMA block exactly the way UnityPy does it. // Mirrors combine_ref.py / combine.js (Node test version) byte-for-byte. const BUNDLE_NODE_NAME_CAB = 'CAB-8c86072a46177f8fda846b38dd6c1698'; const BUNDLE_TARGET_SIZE = 1202382; function bcConcatBytes(arrays) { let total = 0; for (const a of arrays) total += a.length; const out = new Uint8Array(total); let off = 0; for (const a of arrays) { out.set(a, off); off += a.length; } return out; } function bcWriteUInt32BE(buf, offset, value) { buf[offset] = (value >>> 24) & 0xFF; buf[offset+1] = (value >>> 16) & 0xFF; buf[offset+2] = (value >>> 8) & 0xFF; buf[offset+3] = value & 0xFF; } function bcWriteUInt32LE(buf, offset, value) { buf[offset] = value & 0xFF; buf[offset+1] = (value >>> 8) & 0xFF; buf[offset+2] = (value >>> 16) & 0xFF; buf[offset+3] = (value >>> 24) & 0xFF; } function bcWriteInt64BE(buf, offset, value) { const dv = new DataView(buf.buffer, buf.byteOffset + offset, 8); dv.setBigInt64(0, BigInt(value), false); } function bcWriteUInt16BE(buf, offset, value) { buf[offset] = (value >>> 8) & 0xFF; buf[offset+1] = value & 0xFF; } function bcStrToBytes(s) { const out = new Uint8Array(s.length); for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i); return out; } function bcBase64ToBytes(b64) { const bin = atob(b64); const arr = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i); return arr; } // Compresses `data` the way UnityPy compresses each UnityFS block: a 5-byte // header (properties byte + little-endian dict_size) followed by the raw // headerless LZMA1 stream. LZMA-JS's compress() actually returns the full // ".lzma" ALONE-format output (1-byte props + 4-byte dict_size + 8-byte // uncompressed-size + stream); we keep the first 5 bytes as our header and // drop the redundant 8-byte size field (already tracked in block_info). function bcLzmaCompressBlock(data, mode, onProgress) { return new Promise((resolve, reject) => { window.LZMA.compress(Array.from(data), mode, (result, error) => { if (error) { reject(error); return; } const full = new Uint8Array(result.length); for (let i = 0; i < result.length; i++) full[i] = result[i] & 0xFF; const header = full.subarray(0, 5); const rawStream = full.subarray(13); resolve(bcConcatBytes([header, rawStream])); }, onProgress || null); }); } /** * buildCombinedBundle: combine any number of selected items' patches into one * final UnityFS bundle. * * @param patchesById {path_id_str: Uint8Array} merged across all selected items * @param base {metadata, dataArea, ress, res: Uint8Array} (decoded once) * @param objectTable array of [path_id_str, byte_start, byte_size, meta_pos] * @param mode LZMA mode 1-9 * @param onProgress (stage, pct) => void */ async function buildCombinedBundle(patchesById, base, objectTable, mode, onProgress, targetSize) { targetSize = targetSize || BUNDLE_TARGET_SIZE; const { metadata, dataArea, ress, res } = base; const report = (stage, pct) => { if (onProgress) onProgress(stage, pct); }; const sortedObjs = objectTable.slice().sort((a, b) => a[1] - b[1]); const chunks = []; let curLen = 0; const newOffsets = {}; for (const [pidStr, byteStart, byteSize] of sortedObjs) { const align = (8 - (curLen % 8)) % 8; if (align > 0) { chunks.push(new Uint8Array(align)); curLen += align; } const newStart = curLen; let bytesToWrite; if (Object.prototype.hasOwnProperty.call(patchesById, pidStr)) { bytesToWrite = patchesById[pidStr]; } else { bytesToWrite = dataArea.subarray(byteStart, byteStart + byteSize); } chunks.push(bytesToWrite); curLen += bytesToWrite.length; newOffsets[pidStr] = [newStart, bytesToWrite.length]; } const newDataArea = bcConcatBytes(chunks); const newMeta = new Uint8Array(metadata); for (const [pidStr, , , metaPos] of objectTable) { const [ns, nsz] = newOffsets[pidStr]; bcWriteUInt32LE(newMeta, metaPos + 8, ns); bcWriteUInt32LE(newMeta, metaPos + 12, nsz); } const newCabSize = newMeta.length + newDataArea.length; bcWriteUInt32BE(newMeta, 4, newCabSize); const newCab = bcConcatBytes([newMeta, newDataArea]); report('combining', 10); const combinedBlob = bcConcatBytes([newCab, ress, res]); report('compressing', 15); const compressedBlob = await bcLzmaCompressBlock(combinedBlob, mode, (pct) => { report('compressing', 15 + pct * 70); }); report('compressing', 85); const cabNameBytes = bcStrToBytes(BUNDLE_NODE_NAME_CAB); const biParts = []; biParts.push(new Uint8Array(16)); let bi = new Uint8Array(4); bcWriteUInt32BE(bi, 0, 1); biParts.push(bi); let blk = new Uint8Array(10); bcWriteUInt32BE(blk, 0, combinedBlob.length); bcWriteUInt32BE(blk, 4, compressedBlob.length); bcWriteUInt16BE(blk, 8, 1); biParts.push(blk); let nb = new Uint8Array(4); bcWriteUInt32BE(nb, 0, 3); biParts.push(nb); function nodeBytes(offset, size, flags, nameSuffix) { const head = new Uint8Array(8 + 8 + 4); bcWriteInt64BE(head, 0, offset); bcWriteInt64BE(head, 8, size); bcWriteUInt32BE(head, 16, flags); const nameBytes = bcStrToBytes(BUNDLE_NODE_NAME_CAB + nameSuffix); const nul = new Uint8Array(1); return bcConcatBytes([head, nameBytes, nul]); } biParts.push(nodeBytes(0, newCab.length, 4, '')); biParts.push(nodeBytes(newCab.length, ress.length, 0, '.resS')); biParts.push(nodeBytes(newCab.length + ress.length, res.length, 0, '.resource')); const blockInfoRaw = bcConcatBytes(biParts); const blockInfoCompressed = await bcLzmaCompressBlock(blockInfoRaw, 9, null); report('finalizing', 90); const totalPayload = 50 + blockInfoCompressed.length + compressedBlob.length; const padding = targetSize - totalPayload; if (padding < 0) { throw new Error( 'This combination is too large to fit (' + totalPayload + ' bytes, ' + (-padding) + ' bytes over the ' + targetSize + ' byte limit). ' + 'Try selecting fewer items at once.' ); } const header = new Uint8Array(50); header.set(bcStrToBytes('UnityFS\x00'), 0); bcWriteUInt32BE(header, 8, 6); let pos = 12; for (const s of ['5.x.x\x00', '2018.4.11f1\x00']) { const b = bcStrToBytes(s); header.set(b, pos); pos += b.length; } bcWriteInt64BE(header, pos, totalPayload); pos += 8; bcWriteUInt32BE(header, pos, blockInfoCompressed.length); pos += 4; bcWriteUInt32BE(header, pos, blockInfoRaw.length); pos += 4; bcWriteUInt32BE(header, pos, 0x41); pos += 4; report('finalizing', 98); const final = bcConcatBytes([header, blockInfoCompressed, compressedBlob, new Uint8Array(padding)]); report('done', 100); return final; } // ============================================================ // Minimal ZIP writer -- packages the finished bundle into the exact // on-device folder structure the game expects, as a STORED (uncompressed) // zip entry. No external library needed for stored-only entries. // ============================================================ function bcCrc32(bytes) { if (!bcCrc32._table) { const table = new Uint32Array(256); for (let n = 0; n < 256; n++) { let c = n; for (let k = 0; k < 8; k++) { c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1); } table[n] = c; } bcCrc32._table = table; } const table = bcCrc32._table; let crc = 0xFFFFFFFF; for (let i = 0; i < bytes.length; i++) { crc = table[(crc ^ bytes[i]) & 0xFF] ^ (crc >>> 8); } return (crc ^ 0xFFFFFFFF) >>> 0; } function bcDosDateTime(date) { const time = ((date.getHours() & 0x1F) << 11) | ((date.getMinutes() & 0x3F) << 5) | ((date.getSeconds() >> 1) & 0x1F); const day = (((date.getFullYear() - 1980) & 0x7F) << 9) | (((date.getMonth() + 1) & 0xF) << 5) | (date.getDate() & 0x1F); return { time, day }; } function bcWriteU16LE(buf, offset, value) { buf[offset] = value & 0xFF; buf[offset + 1] = (value >>> 8) & 0xFF; } function bcWriteU32LE(buf, offset, value) { buf[offset] = value & 0xFF; buf[offset + 1] = (value >>> 8) & 0xFF; buf[offset + 2] = (value >>> 16) & 0xFF; buf[offset + 3] = (value >>> 24) & 0xFF; } function bcPathToBytes(path) { const out = new Uint8Array(path.length); for (let i = 0; i < path.length; i++) out[i] = path.charCodeAt(i) & 0xFF; return out; } function buildZip(entries) { const { time, day } = bcDosDateTime(new Date()); const localParts = []; const centralParts = []; let offset = 0; let centralSize = 0; for (const entry of entries) { const nameBytes = bcPathToBytes(entry.path); const data = entry.data; const crc = bcCrc32(data); const size = data.length; const localHeader = new Uint8Array(30); bcWriteU32LE(localHeader, 0, 0x04034b50); bcWriteU16LE(localHeader, 4, 20); bcWriteU16LE(localHeader, 6, 0); bcWriteU16LE(localHeader, 8, 0); bcWriteU16LE(localHeader, 10, time); bcWriteU16LE(localHeader, 12, day); bcWriteU32LE(localHeader, 14, crc); bcWriteU32LE(localHeader, 18, size); bcWriteU32LE(localHeader, 22, size); bcWriteU16LE(localHeader, 26, nameBytes.length); bcWriteU16LE(localHeader, 28, 0); localParts.push(localHeader, nameBytes, data); const localEntrySize = localHeader.length + nameBytes.length + data.length; const centralHeader = new Uint8Array(46); bcWriteU32LE(centralHeader, 0, 0x02014b50); bcWriteU16LE(centralHeader, 4, 20); bcWriteU16LE(centralHeader, 6, 20); bcWriteU16LE(centralHeader, 8, 0); bcWriteU16LE(centralHeader, 10, 0); bcWriteU16LE(centralHeader, 12, time); bcWriteU16LE(centralHeader, 14, day); bcWriteU32LE(centralHeader, 16, crc); bcWriteU32LE(centralHeader, 20, size); bcWriteU32LE(centralHeader, 24, size); bcWriteU16LE(centralHeader, 28, nameBytes.length); bcWriteU16LE(centralHeader, 30, 0); bcWriteU16LE(centralHeader, 32, 0); bcWriteU16LE(centralHeader, 34, 0); bcWriteU16LE(centralHeader, 36, 0); bcWriteU32LE(centralHeader, 38, 0); bcWriteU32LE(centralHeader, 42, offset); centralParts.push(centralHeader, nameBytes); centralSize += centralHeader.length + nameBytes.length; offset += localEntrySize; } const centralDirOffset = offset; const eocd = new Uint8Array(22); bcWriteU32LE(eocd, 0, 0x06054b50); bcWriteU16LE(eocd, 4, 0); bcWriteU16LE(eocd, 6, 0); bcWriteU16LE(eocd, 8, entries.length); bcWriteU16LE(eocd, 10, entries.length); bcWriteU32LE(eocd, 12, centralSize); bcWriteU32LE(eocd, 16, centralDirOffset); bcWriteU16LE(eocd, 20, 0); return bcConcatBytes([...localParts, ...centralParts, eocd]); }