import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * PFS/0.9 file archive decoder
 * Author:  Daniel Urban <durban77@gmail.com>
 * License: Public domain
 * Based on Domen Puncer's pfs.c 
 * Big endian version.
 **/
public class PFS {
	private static class Entry {
		public String path;
		public long wtf;
		public long offset;
		public long size;
		public byte[] content = null;
		public Entry(FileInputStream fis, int pathLength) throws IOException {
			byte[] pathBytes = new byte[pathLength];
			fis.read(pathBytes);
			path = new String(pathBytes);
			path = path.substring(0, path.indexOf(0));
			byte[] tmp4 = new byte[4];
			fis.read(tmp4);
			wtf = (0x000000FF & (int)tmp4[0]) | (0x000000FF & (int)tmp4[1])<<8 | (0x000000FF & (int)tmp4[2])<<16 | (0x000000FF & (int)tmp4[3])<<24;
			fis.read(tmp4);
			offset = (0x000000FF & (int)tmp4[0]) | (0x000000FF & (int)tmp4[1])<<8 | (0x000000FF & (int)tmp4[2])<<16 | (0x000000FF & (int)tmp4[3])<<24;
			fis.read(tmp4);
			size = (0x000000FF & (int)tmp4[0]) | (0x000000FF & (int)tmp4[1])<<8 | (0x000000FF & (int)tmp4[2])<<16 | (0x000000FF & (int)tmp4[3])<<24;
		}
		public String toString() {
			return path+"\n"+
			//"wtf:"+wtf+"\n"+
			"offset:"+offset+"\n"+
			"size:"+size+"\n";
		}
	}
	public static void main(String[] args) throws IOException {
		if (args == null || args.length < 1) throw new RuntimeException("Usage: PSF <inputFile>");
		File inputFile = new File(args[0]);
		if (!inputFile.canRead()) throw new RuntimeException(args[0] + " not readable!");
		int headerSize = 0;
		FileInputStream fileInputStream = new FileInputStream(inputFile);
		// read header
		byte[] pfsHeaderBytes = new byte[8];
		fileInputStream.read(pfsHeaderBytes);
		String pfsHeader = new String(pfsHeaderBytes);
		System.out.println("PFS header is : "+pfsHeader);
		byte[] tmp2 = new byte[2];
		fileInputStream.read(tmp2); // wtf1/1
		fileInputStream.read(tmp2); // wtf1/2
		fileInputStream.read(tmp2); // pathlength
		int pathLength =(0x000000FF & (int)tmp2[0]) | (0x000000FF & (int)tmp2[1])<<8;
		System.out.println("Path length : "+pathLength);
		fileInputStream.read(tmp2); // entries
		int numberOfEntries =(0x000000FF & (int)tmp2[0]) | (0x000000FF & (int)tmp2[1])<<8;
		System.out.println("Entries : "+numberOfEntries);
		headerSize = 8+2+2+2+2;
		// read entries
		Entry[] entries = new Entry[numberOfEntries];
		for (int i = 0; i < numberOfEntries; i++) {
			entries[i] = new Entry(fileInputStream, pathLength);
		}
		fileInputStream.close();
		headerSize += (pathLength+4+4+4)*numberOfEntries;
		// read entry content
		RandomAccessFile randomAccessFile = new RandomAccessFile(inputFile, "r");
		for (int i = 0; i < numberOfEntries; i++) {
			System.out.println(entries[i]);
			if (entries[i].size > 0) {
				randomAccessFile.seek(entries[i].offset+headerSize);
				entries[i].content = new byte[(int)entries[i].size];
				randomAccessFile.read(entries[i].content);
			}
		}
		randomAccessFile.close();
		// write entry content 
		for (int i = 0; i < numberOfEntries; i++) {
			File outputFile = new File(inputFile.getParent()+File.separator+entries[i].path.replace(File.separatorChar, '.'));
			if (!outputFile.exists()) 
				outputFile.createNewFile();
			FileOutputStream outputFileStream = new FileOutputStream(outputFile);
			if (entries[i].content != null)
				outputFileStream.write(entries[i].content);
			outputFileStream.flush();
			outputFileStream.close();
		}
	}

}
