Skip to content

Commit

Permalink
Support Jar file data prefixes when exporting
Browse files Browse the repository at this point in the history
For cases like Jar2Exe
  • Loading branch information
Col-E committed Apr 27, 2024
1 parent d92a543 commit 4a6cc16
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package software.coley.recaf.info.properties.builtin;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import software.coley.recaf.info.Info;
import software.coley.recaf.info.properties.BasicProperty;
import software.coley.recaf.info.properties.Property;

/**
* Built in property to track data appearing before the ZIP header in an archive.
*
* @author Matt Coley
*/
public class ZipPrefixDataProperty extends BasicProperty<byte[]> {
public static final String KEY = "zip-prefix-data";

/**
* @param data
* Optional data.
*/
public ZipPrefixDataProperty(@Nullable byte[] data) {
super(KEY, data);
}

/**
* @param info
* Info instance.
*
* @return Optional data.
* {@code null} when no property value is assigned.
*/
@Nullable
public static byte[] get(@Nonnull Info info) {
Property<byte[]> property = info.getProperty(KEY);
if (property != null) {
return property.value();
}
return null;
}

/**
* @param info
* Info instance.
* @param value
* Optional data.
*/
public static void set(@Nonnull Info info, @Nonnull byte[] value) {
info.setProperty(new ZipPrefixDataProperty(value));
}

/**
* @param info
* Info instance.
*/
public static void remove(@Nonnull Info info) {
info.removeProperty(KEY);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import software.coley.lljzip.format.model.CentralDirectoryFileHeader;
import software.coley.lljzip.format.model.ZipArchive;
import software.coley.lljzip.util.ExtraFieldTime;
import software.coley.lljzip.util.MemorySegmentUtil;
import software.coley.recaf.analytics.logging.Logging;
import software.coley.recaf.info.*;
import software.coley.recaf.info.builder.FileInfoBuilder;
Expand All @@ -21,6 +22,7 @@

import java.io.File;
import java.io.IOException;
import java.lang.foreign.MemorySegment;
import java.net.URL;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
Expand Down Expand Up @@ -130,11 +132,17 @@ private WorkspaceFileResource handleZip(WorkspaceFileResourceBuilder builder, Zi
ZipArchive archive = config.mapping().apply(source.readAll());

// Sanity check, if there's data at the head of the file AND its otherwise empty its probably junk.
if (archive.getPrefixData() != null && archive.getEnd() != null && archive.getParts().size() == 1) {
MemorySegment prefixData = archive.getPrefixData();
if (prefixData != null && archive.getEnd() != null && archive.getParts().size() == 1) {
// We'll throw as the caller should catch this case and handle it based on their needs.
throw new IOException("Content matched ZIP header but had no file entries");
}

// Record prefix data to attribute held by the zip file info.
if (prefixData != null) {
ZipPrefixDataProperty.set(zipInfo, MemorySegmentUtil.toByteArray(prefixData));
}

// Build model from the contained files in the ZIP
archive.getLocalFiles().forEach(header -> {
LocalFileHeaderSource headerSource = new LocalFileHeaderSource(header, isAndroid);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import jakarta.annotation.Nonnull;
import software.coley.recaf.info.*;
import software.coley.recaf.info.properties.builtin.*;
import software.coley.recaf.services.workspace.WorkspaceManager;
import software.coley.recaf.util.Unchecked;
import software.coley.recaf.util.ZipCreationUtils;
import software.coley.recaf.services.workspace.WorkspaceManager;
import software.coley.recaf.workspace.model.Workspace;
import software.coley.recaf.workspace.model.bundle.AndroidClassBundle;
import software.coley.recaf.workspace.model.bundle.JvmClassBundle;
Expand All @@ -18,6 +18,7 @@
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
Expand Down Expand Up @@ -136,6 +137,7 @@ private class WorkspaceExporterImpl implements WorkspaceExporter {
private final Map<String, Long> modifyTimes = new HashMap<>();
private final Map<String, Long> createTimes = new HashMap<>();
private final Map<String, Long> accessTimes = new HashMap<>();
private byte[] prefix;

@Override
public void export(@Nonnull Workspace workspace) throws IOException {
Expand Down Expand Up @@ -163,7 +165,12 @@ public void export(@Nonnull Workspace workspace) throws IOException {
});

// Write buffer to path
Files.write(path, zipBuilder.bytes());
if (prefix != null) {
Files.write(path, prefix);
Files.write(path, zipBuilder.bytes(), StandardOpenOption.APPEND);
} else {
Files.write(path, zipBuilder.bytes());
}
break;
case DIRECTORY:
for (Map.Entry<String, byte[]> entry : contents.entrySet()) {
Expand All @@ -186,12 +193,19 @@ public void export(@Nonnull Workspace workspace) throws IOException {
* Workspace to pull data from.
*/
private void populate(@Nonnull Workspace workspace) {
// If shading libs, they go first so the primary content will be the authoritative copy for
// any duplicate paths held by both resources.
if (bundleSupporting) {
for (WorkspaceResource supportingResource : workspace.getSupportingResources()) {
mapInto(contents, supportingResource);
}
}
mapInto(contents, workspace.getPrimaryResource());
WorkspaceResource primary = workspace.getPrimaryResource();
mapInto(contents, primary);

// If the resource had prefix data, get it here so that we can write it back later.
if (primary instanceof WorkspaceFileResource resource)
prefix = ZipPrefixDataProperty.get(resource.getFileInfo());
}

/**
Expand Down

0 comments on commit 4a6cc16

Please sign in to comment.