package org.ddth.game;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;

/**
 * Unpack DoSWF encrypted Flash - Useful for hacking dovogame ;-)
 * 
 * @author instcode
 */
public class DoSWFUnpacker {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		DoSWFUnpacker decrypter = new DoSWFUnpacker();
		try {
			File inputFile = new File(args[0]);
			InputStream inputStream = new FileInputStream(inputFile);
			ByteBuffer buffer = ByteBuffer.allocate((int) inputFile.length());
			inputStream.read(buffer.array());
			decrypter.handle(buffer, new File("./output.swf"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Decompress CWS to FWS
	 * 
	 * CWS (compressed SWF)
	 * FWS (uncompressed SWF)
	 * 
	 * @param data
	 */
	public static void decompress(byte[] data, File outputFile) {
		// Lazily assuming that the compression ratio is about 90%
		byte[] output = new byte[data.length * 10];
		int size = 0;
		try {

			Inflater decompresser = new Inflater();
			String signature = new String(data, 0, 3);
			if (signature.equals("CWS")) {
				decompresser.setInput(data, 8, data.length - 8);
				size = decompresser.inflate(output);
				decompresser.end();

				OutputStream out = new FileOutputStream(outputFile);
				out.write(new byte[] { 'F', 'W', 'S' });
				out.write(data, 3, 5);
				out.write(output, 0, size);
				out.close();
			}
		} catch (DataFormatException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void unpack(ByteBuffer buffer, File outputFile) {
		ByteBuffer plain = decrypt(buffer);
		handle(plain, outputFile);
	}

	private void handle(ByteBuffer buffer, File outputFile) {
		byte nonce = buffer.get();
		if (nonce > 0) {
			removeNonce(buffer);
		}

		byte bool = buffer.get();
		int size = buffer.getInt();

		byte[] head = new byte[size];
		buffer.get(head, 0, size);

		byte[] tail = new byte[buffer.remaining()];
		buffer.get(tail);

		byte[] data = bool > 0 ? head : tail;
		decompress(data, outputFile);
	}

	private ByteBuffer removeNonce(ByteBuffer buffer) {
		int max = buffer.getInt();
		int mark = buffer.getInt();
		int length = buffer.getInt();

		ByteBuffer data = ByteBuffer.allocate(length);
		data.order(ByteOrder.LITTLE_ENDIAN);
		buffer.get(data.array(), 0, length);

		ByteBuffer output = ByteBuffer.allocate(max * 2);
		while (output.position() < max) {
			data.position(4);
			data.putInt((int) (mark * 3 / 4 + Math.random() * mark / 2));
			output.put(data.array());
		}
		return output;
	}

	private ByteBuffer decrypt(ByteBuffer buffer) {
		byte block_size = (byte) (buffer.get() - 1);
		byte key = (byte) (buffer.get() - 3);

		int offset = buffer.getInt();
		int length = buffer.getInt() - 2;

		byte[] data = new byte[length];
		buffer.position(buffer.limit() - length);
		buffer.get(data);
		for (int count = 0; count < length;) {
			for (int i = 0; i < block_size; i += 4) {
				data[count] = (byte) (data[count] ^ key);
				++count;
				if (count >= length) {
					break;
				}
			}
			count = count + offset;
		}

		ByteBuffer output = ByteBuffer.allocate(data.length * 5);
		try {
			Inflater decompresser = new Inflater();
			decompresser.setInput(data, 0, data.length);
			int size = decompresser.inflate(output.array());
			output.limit(size);
			output.position(0);
			decompresser.end();
		} catch (DataFormatException e) {
			e.printStackTrace();
		}
		return output;
	}
}