blob: fc5cc014383c92c1ef3eca951032e4bfc2df5535 [file] [log] [blame]
/*
* Created on 28.11.2005
*/
package org.eclipse.epp.installer.tools.packager;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
/*
* Self-Extracting File Structure
*
* |---------------------------|
* | Executable Extractor |
* | |
* |---------------------------|
* | Files Data |
* | |
* |---------------------------|
* | SFX Header |
* | |
* |---------------------------|
* | Table Of Contents |
* | (TOC) |
* |---------------------------|
* | SFX Footer |
* | |
* |---------------------------|
*
*
* SFX Header:
* 12 bytes - SFX signature ( including format version )
* 4 bytes - number of files
*
* TOC Entry:
* 4 bytes - file data offset
* 4 bytes - file data size
* 4 bytes - file flags
* 4 bytes - filename length (N)
* N bytes - filename
*
* SFX Footer:
* 4 bytes - SFX header offset
* 4 bytes - 32-bit complementary checksum
*/
/**
* <p>
* Copyright (c) 2006, Instantiations, Inc.<br>
* All Rights Reserved
*
* @author Max Stepanov (max@xored.com)
*/
public class NativePackager {
public static final String FORMAT_VERSION = "0.1";
private static final String SIGNATURE = "!SFX.PKG-";
private static final int FLAGS_EXTRACT = 0x0001;
private class Entry {
File file;
String name;
int size;
int offset;
int flags;
public Entry( File file) {
this(file,0);
}
public Entry( File file, int flags ) {
this(file,file.getName(),flags);
}
public Entry( File file, String name, int flags ) {
this.file = file;
this.name = name;
this.flags = flags;
}
}
private class ChecksumOutputStream extends FilterOutputStream {
private int checksum = 0;
private int index = 0;
public ChecksumOutputStream(OutputStream out) {
super(out);
}
public void write(byte[] b, int off, int len) throws IOException {
super.write(b, off, len);
calc(b, off, len);
}
private void calc(byte[] b, int off, int len) {
while( index != 0 && len > 0 ) {
calc(b[off++]);
--len;
}
for( ; len >= 4 ; len-=4 ) {
checksum += ((int)b[off++]) << 24;
checksum += (((int)b[off++]) << 16) & 0xFF0000;
checksum += (((int)b[off++]) << 8) & 0xFF00;
checksum += ((int)b[off++]) & 0xFF;
}
while( len > 0 ) {
calc(b[off++]);
--len;
}
}
private void calc(byte b) {
switch ( index ) {
case 0:
checksum += ((int)b) << 24;
index = 1;
break;
case 1:
checksum += (((int)b) << 16) & 0xFF0000;
index = 2;
break;
case 2:
checksum += (((int)b) << 8) & 0xFF00;
index = 3;
break;
case 3:
checksum += ((int)b) & 0xFF;
index = 0;
break;
}
}
public int getChecksum() {
return checksum;
}
public int getIndex() {
return index;
}
}
private ArrayList entries = new ArrayList();
public NativePackager( File container ) {
/* entry 0 is a fake entry for container */
entries.add( new Entry(container));
}
public void addFile( File file ) {
if ( file == null )
throw new IllegalArgumentException();
entries.add( new Entry(file));
}
public void addFile( File file, boolean extract ) {
if ( file == null )
throw new IllegalArgumentException();
entries.add( new Entry(file,extract ? FLAGS_EXTRACT : 0));
}
public void build( File dest ) throws IOException {
if ( dest == null )
throw new IllegalArgumentException();
if ( dest.exists() )
dest.delete();
ChecksumOutputStream out = new ChecksumOutputStream(new BufferedOutputStream( new FileOutputStream(dest)));
/* write data */
int n;
int offset = 0;
byte[] buffer = new byte[1024];
for( Iterator i = entries.iterator(); i.hasNext(); ) {
Entry entry = (Entry) i.next();
InputStream in = new FileInputStream(entry.file);
entry.offset = offset;
while( (n = in.read(buffer)) > 0 ) {
out.write(buffer,0,n);
offset += n;
}
entry.size = offset - entry.offset;
in.close();
}
/* remove fake entry 0 */
entries.remove(0);
/* write SFX Header */
out.write((SIGNATURE+FORMAT_VERSION).getBytes());
out.write(toArray32(entries.size()));
/* write TOC */
for( Iterator i = entries.iterator(); i.hasNext(); ) {
Entry entry = (Entry) i.next();
out.write(toArray32(entry.offset));
out.write(toArray32(entry.size));
out.write(toArray32(entry.flags));
byte[] filename = entry.name.getBytes();
out.write(toArray32(filename.length));
out.write(filename);
}
/* align to 4 bytes */
if ( out.getIndex() != 0 )
out.write( new byte[4-out.getIndex()]);
/* write SFX Footer */
out.write(toArray32(offset));
out.write(toArray32(-1-out.getChecksum()));
out.close();
}
private static byte[] toArray32(int value) {
byte[] data = new byte[4];
data[0] = (byte)( value >>> 24 );
data[1] = (byte)( value >>> 16 );
data[2] = (byte)( value >>> 8 );
data[3] = (byte)( value );
return data;
}
public static void main( String args[] ) {
if ( args.length < 3 ) {
System.out.println("Usage:\nNativePackager dest src [-x]file1 [-x]file2 ...");
return;
}
NativePackager p = new NativePackager( new File(args[1]) );
for( int i = 2; i < args.length; ++i ) {
String file = args[i];
if ( file.startsWith("-x") ) {
p.addFile( new File(file.substring(2)), true );
} else {
p.addFile( new File(file) );
}
}
try {
p.build( new File(args[0]) );
System.out.println("Packaging complete.");
} catch (IOException e) {
e.printStackTrace();
// Exit with non-zero status so that the build will fail
System.exit(1);
}
}
}