1. Add assemblies.blob's unpack logic
This commit is contained in:
parent
073027a130
commit
9ee747971f
|
@ -0,0 +1,13 @@
|
||||||
|
package com.zane.smapiinstaller.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class AssemblyStoreAssembly {
|
||||||
|
private Integer dataOffset;
|
||||||
|
private Integer dataSize;
|
||||||
|
private Integer debugDataOffset;
|
||||||
|
private Integer debugDataSize;
|
||||||
|
private Integer configDataOffset;
|
||||||
|
private Integer configDataSize;
|
||||||
|
}
|
|
@ -1,9 +1,14 @@
|
||||||
package com.zane.smapiinstaller.utils;
|
package com.zane.smapiinstaller.utils;
|
||||||
|
|
||||||
|
import com.google.common.base.CharMatcher;
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
import com.google.common.io.ByteSource;
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import com.zane.smapiinstaller.dto.AssemblyStoreAssembly;
|
||||||
import com.zane.smapiinstaller.dto.Tuple2;
|
import com.zane.smapiinstaller.dto.Tuple2;
|
||||||
|
|
||||||
import net.fornwall.apksigner.zipio.ZioEntry;
|
import net.fornwall.apksigner.zipio.ZioEntry;
|
||||||
|
@ -19,13 +24,17 @@ import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -36,13 +45,18 @@ import lombok.EqualsAndHashCode;
|
||||||
*/
|
*/
|
||||||
public class ZipUtils {
|
public class ZipUtils {
|
||||||
|
|
||||||
private final static String FILE_HEADER_XALZ = "XALZ";
|
private final static byte[] MAGIC_BLOB = new byte[]{'X', 'A', 'B', 'A'};
|
||||||
|
private final static byte[] MAGIC_COMPRESSED = new byte[]{'X', 'A', 'L', 'Z'};
|
||||||
|
|
||||||
|
public static int fromBytes(byte[] bytes) {
|
||||||
|
return (bytes[0] & 0xff) | ((bytes[1] & 0xff) << 8) | ((bytes[2] & 0xff) << 16) | ((bytes[3] & 0xff) << 24);
|
||||||
|
}
|
||||||
|
|
||||||
public static byte[] decompressXALZ(byte[] bytes) {
|
public static byte[] decompressXALZ(byte[] bytes) {
|
||||||
if (bytes == null) {
|
if (bytes == null) {
|
||||||
return new byte[0];
|
return new byte[0];
|
||||||
}
|
}
|
||||||
if (FILE_HEADER_XALZ.equals(new String(ByteUtils.subArray(bytes, 0, 4), StandardCharsets.ISO_8859_1))) {
|
if (Arrays.equals(ByteUtils.subArray(bytes, 0, 4), MAGIC_COMPRESSED)) {
|
||||||
byte[] length = ByteUtils.subArray(bytes, 8, 12);
|
byte[] length = ByteUtils.subArray(bytes, 8, 12);
|
||||||
int len = (length[0] & 0xff) | ((length[1] & 0xff) << 8) | ((length[2] & 0xff) << 16) | ((length[3] & 0xff) << 24);
|
int len = (length[0] & 0xff) | ((length[1] & 0xff) << 8) | ((length[2] & 0xff) << 16) | ((length[3] & 0xff) << 24);
|
||||||
bytes = LZ4Factory.fastestJavaInstance().fastDecompressor().decompress(bytes, 12, len);
|
bytes = LZ4Factory.fastestJavaInstance().fastDecompressor().decompress(bytes, 12, len);
|
||||||
|
@ -50,6 +64,60 @@ public class ZipUtils {
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Map<String, byte[]> unpackXABA(byte[] manifestBytes, byte[] xabaBytes) {
|
||||||
|
List<List<String>> manifest = Splitter.on('\n').omitEmptyStrings().splitToList(new String(manifestBytes, StandardCharsets.UTF_8))
|
||||||
|
.stream().skip(1).map(line -> Splitter.on(CharMatcher.whitespace()).omitEmptyStrings().splitToList(line)).collect(Collectors.toList());
|
||||||
|
ByteSource source = ByteSource.wrap(manifestBytes);
|
||||||
|
Map<String, byte[]> result = new HashMap<>();
|
||||||
|
try {
|
||||||
|
int offset = 0;
|
||||||
|
byte[] buffer = source.slice(offset, 4).read();
|
||||||
|
if (!Arrays.equals(buffer, MAGIC_BLOB)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
buffer = source.slice(offset += 4, 4).read();
|
||||||
|
int version = fromBytes(buffer);
|
||||||
|
if (version > 1) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
buffer = source.slice(offset += 4, 4).read();
|
||||||
|
int lec = fromBytes(buffer);
|
||||||
|
buffer = source.slice(offset += 4, 4).read();
|
||||||
|
int gec = fromBytes(buffer);
|
||||||
|
buffer = source.slice(offset += 4, 4).read();
|
||||||
|
int storeId = fromBytes(buffer);
|
||||||
|
for (int i = 0; i < lec; i++) {
|
||||||
|
AssemblyStoreAssembly assembly = new AssemblyStoreAssembly();
|
||||||
|
buffer = source.slice(offset += 4, 4).read();
|
||||||
|
assembly.setDataOffset(fromBytes(buffer));
|
||||||
|
buffer = source.slice(offset += 4, 4).read();
|
||||||
|
assembly.setDataSize(fromBytes(buffer));
|
||||||
|
buffer = source.slice(offset += 4, 4).read();
|
||||||
|
assembly.setDebugDataOffset(fromBytes(buffer));
|
||||||
|
buffer = source.slice(offset += 4, 4).read();
|
||||||
|
assembly.setDebugDataSize(fromBytes(buffer));
|
||||||
|
buffer = source.slice(offset += 4, 4).read();
|
||||||
|
assembly.setConfigDataOffset(fromBytes(buffer));
|
||||||
|
buffer = source.slice(offset += 4, 4).read();
|
||||||
|
assembly.setConfigDataSize(fromBytes(buffer));
|
||||||
|
|
||||||
|
buffer = source.slice(assembly.getDataOffset(), 4).read();
|
||||||
|
byte[] bytes;
|
||||||
|
if (Arrays.equals(buffer, MAGIC_COMPRESSED)) {
|
||||||
|
byte[] lzBytes = source.slice(assembly.getDataOffset(), assembly.getDataSize()).read();
|
||||||
|
bytes = decompressXALZ(lzBytes);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
bytes = source.slice(assembly.getDataOffset(), assembly.getDataSize()).read();
|
||||||
|
}
|
||||||
|
result.put(manifest.get(i).get(4) + ".dll", bytes);
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public static Tuple2<byte[], Set<String>> addOrReplaceEntries(String inputZipFilename, List<ZipEntrySource> entrySources, String outputZipFilename, Function<String, Boolean> removePredict, Consumer<Integer> progressCallback) throws IOException {
|
public static Tuple2<byte[], Set<String>> addOrReplaceEntries(String inputZipFilename, List<ZipEntrySource> entrySources, String outputZipFilename, Function<String, Boolean> removePredict, Consumer<Integer> progressCallback) throws IOException {
|
||||||
File inFile = new File(inputZipFilename).getCanonicalFile();
|
File inFile = new File(inputZipFilename).getCanonicalFile();
|
||||||
File outFile = new File(outputZipFilename).getCanonicalFile();
|
File outFile = new File(outputZipFilename).getCanonicalFile();
|
||||||
|
@ -77,14 +145,14 @@ public class ZipUtils {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ZioEntry manifest = input.entries.get("META-INF/MANIFEST.MF");
|
ZioEntry manifest = input.entries.get("META-INF/MANIFEST.MF");
|
||||||
if(manifest != null) {
|
if (manifest != null) {
|
||||||
originManifest = manifest.getData();
|
originManifest = manifest.getData();
|
||||||
}
|
}
|
||||||
for (ZioEntry inEntry : input.entries.values()) {
|
for (ZioEntry inEntry : input.entries.values()) {
|
||||||
if (removePredict != null && removePredict.apply(inEntry.getName())) {
|
if (removePredict != null && removePredict.apply(inEntry.getName())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
taskBundle.submitTask(()->{
|
taskBundle.submitTask(() -> {
|
||||||
if (entryMap.containsKey(inEntry.getName())) {
|
if (entryMap.containsKey(inEntry.getName())) {
|
||||||
ZipEntrySource source = entryMap.get(inEntry.getName());
|
ZipEntrySource source = entryMap.get(inEntry.getName());
|
||||||
ZioEntry zioEntry = new ZioEntry(inEntry.getName());
|
ZioEntry zioEntry = new ZioEntry(inEntry.getName());
|
||||||
|
@ -117,7 +185,7 @@ public class ZipUtils {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
for (String name : difference) {
|
for (String name : difference) {
|
||||||
taskBundle.submitTask(()-> {
|
taskBundle.submitTask(() -> {
|
||||||
ZipEntrySource source = entryMap.get(name);
|
ZipEntrySource source = entryMap.get(name);
|
||||||
ZioEntry zioEntry = new ZioEntry(name);
|
ZioEntry zioEntry = new ZioEntry(name);
|
||||||
zioEntry.setCompression(source.getCompressionMethod());
|
zioEntry.setCompression(source.getCompressionMethod());
|
||||||
|
@ -132,9 +200,8 @@ public class ZipUtils {
|
||||||
taskBundle.join();
|
taskBundle.join();
|
||||||
progressCallback.accept(100);
|
progressCallback.accept(100);
|
||||||
}
|
}
|
||||||
}
|
} catch (RuntimeException e) {
|
||||||
catch (RuntimeException e) {
|
if (e.getCause() != null && e.getCause() instanceof IOException) {
|
||||||
if(e.getCause() != null && e.getCause() instanceof IOException) {
|
|
||||||
throw (IOException) e.getCause();
|
throw (IOException) e.getCause();
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
|
|
Loading…
Reference in New Issue