use of info.ata4.junity.bundle.BundleHeader in project disunity by ata4.
the class BundleWriter method write.
public void write(Bundle bundle, Progress progress) throws IOException {
this.bundle = bundle;
// add offset placeholders
levelOffsetMap.clear();
bundle.entries().stream().filter(entry -> {
if (bundle.entries().size() == 1) {
return true;
}
String name = entry.name();
return name.equals("mainData") || name.startsWith("level");
}).forEach(entry -> levelOffsetMap.put(entry, new MutablePair<>(0L, 0L)));
BundleHeader header = bundle.header();
header.levelByteEnd().clear();
header.levelByteEnd().addAll(levelOffsetMap.values());
header.numberOfLevelsToDownload(levelOffsetMap.size());
// write header
out.writeStruct(header);
header.headerSize((int) out.position());
// write bundle data
if (header.compressed()) {
// write data to temporary file
try (DataWriter outData = DataWriters.forFile(dataFile, CREATE, WRITE, TRUNCATE_EXISTING)) {
writeData(outData, progress);
}
// configure LZMA encoder
LzmaEncoderProps props = new LzmaEncoderProps();
// 8 MiB
props.setDictionarySize(1 << 23);
// maximum
props.setNumFastBytes(273);
props.setUncompressedSize(Files.size(dataFile));
props.setEndMarkerMode(true);
// stream the temporary bundle data compressed into the bundle file
try (OutputStream os = new LzmaOutputStream(new BufferedOutputStream(out.stream()), props)) {
Files.copy(dataFile, os);
}
for (MutablePair<Long, Long> levelOffset : levelOffsetMap.values()) {
levelOffset.setLeft(out.size());
}
} else {
// write data directly to file
writeData(out, progress);
}
// update header
int fileSize = (int) out.size();
header.completeFileSize(fileSize);
header.minimumStreamedBytes(fileSize);
out.position(0);
out.writeStruct(header);
}
use of info.ata4.junity.bundle.BundleHeader in project disunity by ata4.
the class BundleProps method read.
static void read(Path propsFile, Bundle bundle) throws IOException {
BundleProps props;
try (Reader reader = Files.newBufferedReader(propsFile, CHARSET)) {
props = new Gson().fromJson(reader, BundleProps.class);
}
BundleHeader header = bundle.header();
header.compressed(props.compressed);
header.streamVersion(props.streamVersion);
header.unityVersion(new UnityVersion(props.unityVersion));
header.unityRevision(new UnityVersion(props.unityRevision));
String bundleName = PathUtils.getBaseName(propsFile);
Path bundleDir = propsFile.resolveSibling(bundleName);
props.files.stream().map(bundleDir::resolve).forEach(file -> {
bundle.entries().add(new BundleExternalEntry(file));
});
}
use of info.ata4.junity.bundle.BundleHeader in project disunity by ata4.
the class BundleProps method write.
static void write(Path propsFile, Bundle bundle) throws IOException {
BundleProps props = new BundleProps();
BundleHeader header = bundle.header();
props.compressed = header.compressed();
props.streamVersion = header.streamVersion();
props.unityVersion = header.unityVersion().toString();
props.unityRevision = header.unityRevision().toString();
props.files = bundle.entryInfos().stream().map(entry -> entry.name()).collect(Collectors.toList());
try (Writer writer = Files.newBufferedWriter(propsFile, CHARSET, WRITE, CREATE, TRUNCATE_EXISTING)) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
gson.toJson(props, writer);
}
}
use of info.ata4.junity.bundle.BundleHeader in project disunity by ata4.
the class BundleReader method read.
public Bundle read() throws BundleException, IOException {
bundle = new Bundle();
in.position(0);
BundleHeader header = bundle.header();
in.readStruct(header);
// check signature
if (!header.hasValidSignature()) {
throw new BundleException("Invalid signature");
}
List<BundleEntryInfo> entryInfos = bundle.entryInfos();
if (header.compressedDataHeaderSize() > 0) {
if (header.dataHeaderAtEndOfFile()) {
in.position(header.completeFileSize() - header.compressedDataHeaderSize());
}
// build an input stream for the uncompressed data header
InputStream headerIn = new BoundedInputStream(in.stream(), header.compressedDataHeaderSize());
DataReader inData;
switch(header.dataHeaderCompressionScheme()) {
default:
case 0:
// Not compressed
inData = DataReaders.forInputStream(headerIn);
case 1:
// LZMA
inData = DataReaders.forInputStream(new CountingInputStream(new LzmaInputStream(headerIn)));
case 3:
// LZ4
byte[] compressed = new byte[header.compressedDataHeaderSize()];
byte[] decompressed = new byte[(int) header.dataHeaderSize()];
headerIn.read(compressed);
LZ4JavaSafeFastDecompressor.INSTANCE.decompress(compressed, decompressed);
inData = DataReaders.forByteBuffer(ByteBuffer.wrap(decompressed));
}
// Block info: not captured for now
{
// 16 bytes unknown
byte[] unknown = new byte[16];
inData.readBytes(unknown);
int storageBlocks = inData.readInt();
for (int i = 0; i < storageBlocks; ++i) {
inData.readUnsignedInt();
inData.readUnsignedInt();
inData.readUnsignedShort();
}
}
int files = inData.readInt();
for (int i = 0; i < files; i++) {
BundleEntryInfo entryInfo = new BundleEntryInfoFS();
inData.readStruct(entryInfo);
entryInfos.add(entryInfo);
}
} else {
// raw or web header
long dataHeaderSize = header.dataHeaderSize();
if (dataHeaderSize == 0) {
// old stream versions don't store the data header size, so use a large
// fixed number instead
dataHeaderSize = 4096;
}
InputStream is = dataInputStream(0, dataHeaderSize);
DataReader inData = DataReaders.forInputStream(is);
int files = inData.readInt();
for (int i = 0; i < files; i++) {
BundleEntryInfo entryInfo = new BundleEntryInfo();
inData.readStruct(entryInfo);
entryInfos.add(entryInfo);
}
}
// sort entries by offset so that they're in the order in which they
// appear in the file, which is convenient for compressed bundles
entryInfos.sort((a, b) -> Long.compare(a.offset(), b.offset()));
List<BundleEntry> entries = bundle.entries();
entryInfos.forEach(entryInfo -> {
entries.add(new BundleInternalEntry(entryInfo, this::inputStreamForEntry));
});
return bundle;
}
use of info.ata4.junity.bundle.BundleHeader in project disunity by ata4.
the class BundleInfo method buildHeaderTable.
private Table<Integer, Integer, Object> buildHeaderTable(BundleHeader header) {
TableBuilder table = new TableBuilder();
table.row("Field", "Value");
table.row("signature", header.signature());
table.row("streamVersion", header.streamVersion());
table.row("unityVersion", header.unityVersion());
table.row("unityRevision", header.unityRevision());
table.row("minimumStreamedBytes", header.minimumStreamedBytes());
table.row("headerSize", header.headerSize());
table.row("numberOfLevelsToDownload", header.numberOfLevelsToDownload());
table.row("numberOfLevels", header.numberOfLevels());
List<Pair<Long, Long>> levelByteEnds = header.levelByteEnd();
for (int i = 0; i < levelByteEnds.size(); i++) {
Pair<Long, Long> levelByteEnd = levelByteEnds.get(i);
table.row("levelByteEnd[" + i + "][0]", levelByteEnd.getLeft());
table.row("levelByteEnd[" + i + "][1]", levelByteEnd.getRight());
}
if (header.streamVersion() >= 2) {
table.row("completeFileSize", header.completeFileSize());
}
if (header.streamVersion() >= 3) {
table.row("dataHeaderSize", header.dataHeaderSize());
}
return table.get();
}
Aggregations