1. Add assemblies.blob's unpack logic

This commit is contained in:
ZaneYork 2023-02-08 21:41:48 +08:00
parent 073027a130
commit 9ee747971f
2 changed files with 88 additions and 8 deletions

View File

@ -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;
}

View File

@ -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;