package com.mpaa.decss;

import java.io.*;


/**
 * Title:        LinearFeedbackShiftRegisterPair.java
 * Description:  Sort of based on JDeCSS Java code originally written by Gavin Hall (www.tweakmonkey.freeserve.co.uk)
 *               The title key cracker is based on Frank Stevenson's work describing various ways to break CSS and a close 
 *               examinination of VobDec code.
 *
 *               This version has been brought to you by DiatriBe.
 *
 *               Note: There is no attempt to do DVD drive authentication in this code.  You should first play the DVD 
 *               with a software player (only for a second, unless you want to watch the whole movie).  Once this is done, 
 *               then authentication has completed.  The VOB files can now be copied!
 * @author DiatriBe
 * @version 1.0
 */

public class CSSDescrambler
{
	LinearFeedbackShiftRegisterPair feedbackRegisterPair = new LinearFeedbackShiftRegisterPair();

	TitleKey titleKey;
	File inputVobFile;
	File outputVobFile;

	/**
	 *  Default constructor.
	 *
	 * @param titleKey the 40 bit key used for the decryption.
	 * @param inputVobFile the source MPEG file.  This can be the file that is directly on the DVD (once the drive has
	 *                     been authenticated, files can be copied directly from it).
	 * @param outputVobFile the file to write the decrpyted stream into.
	 */
	public CSSDescrambler(TitleKey titleKey, File inputVobFile, File outputVobFile)
	{
		this.titleKey = titleKey;
		this.inputVobFile = inputVobFile;
		this.outputVobFile = outputVobFile;
	}

	/**
	 * This method will descramble a file passed in to the constructor as inputVobFile to the file passed in as outputVobFile,
	 * using the title key.
	 */
	public void descramble()
	{
		try
		{
			DataInputStream reader = new DataInputStream(new BufferedInputStream(new FileInputStream(inputVobFile)));
			DataOutputStream writer = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(outputVobFile)));

			long fileSize = inputVobFile.length();
			long bytesRead = 0;

			int[] sector = new int[2048];

			for (int block = 0; bytesRead < fileSize; block++)
			{
				for (int index = 0; index < 2048; index++)
					sector[index] = reader.readUnsignedByte();

				bytesRead += 2048;

				if ((block % 1000) == 0)
					System.out.println("Decrypting block " + block);

				if ((sector[0x14] & 0x30) != 0)       // PES scrambling control
				{
					sector = descrambleSector(sector);
					sector[0x14] &= 0x8F;
				}

				for (int index = 0; index < 2048; index++)
					writer.writeByte(sector[index]);
			}

			reader.close();
			writer.flush();
			writer.close();
		}
		catch (FileNotFoundException fnfe)
		{
			System.out.println("The imput file '" + inputVobFile.getName() + "' could not be found.");
		}
		catch (Exception ex)
		{
			System.out.println("There was an error encountered while trying to decrypt '" + inputVobFile.getName() + "' to '" + outputVobFile.getName() + "'");
			ex.printStackTrace();
		}
	}

	/**
	 *  This method does the actual descrambling of a single sector. The title key is used to decrypt the data.
	 *
	 *  @param  sector int array (2048) containing an encrypted DVD sector.
	 *  @return returns the decrypted sector as a int array (2048).
	 */
	private int[] descrambleSector(int[] sector)
	{
		MirrorBitsTable mirrorTable = MirrorBitsTable.getInstance();

		feedbackRegisterPair.setRegisters(titleKey);
//		feedbackRegisterPair.useClassic(true);

		int[] salt = { sector[0x54], sector[0x55], sector[0x56], sector[0x57], sector[0x58] };
		feedbackRegisterPair.salt(salt);

		int carry = 0;

		for (int index = 128; index < 2048; index++)
		{
			feedbackRegisterPair.clockOneByte();

			int cipherStreamOutput = feedbackRegisterPair.getLfsr17Output(true) + feedbackRegisterPair.getLfsr25Output(false) + carry;

			int tableSubstitution = SubstitutionTable.getAt(sector[index]);
			sector[index] = (cipherStreamOutput ^ tableSubstitution) & 0xFF;

			carry = cipherStreamOutput >> 8;
		}
		return sector;
	}

	/**
	 *  This is the main method for the prgram that will be executed when the program is started.
	 *
	 *  @param args command line arguments.
	 */
	public static void main(String[] args)
	{
		if (args.length != 2)
		{
			System.out.println("Wrong number of arguments (" + args.length + ") passed!");
			System.out.println("Format is CSSDescrambler 'input file name' 'output file name'");
			return;
		}

		File inputFile = new File(args[0]);

		System.out.println();
		System.out.println("Descrambling file '" + args[0] + "' to '" + args[1] + "'");
		System.out.println();
		System.out.println("Blocks in input file = " + (inputFile.length() / 2048));
		System.out.println();

		TitleKeyCracker titleKeyCracker = new TitleKeyCracker(inputFile);

		if (titleKeyCracker.attackKey())
		{
			TitleKey titleKey = titleKeyCracker.getTitleKey();

//			TitleKey titleKey = new TitleKey(0xA7, 0x07, 0xBF, 0x53, 0x84);

			System.out.println("Title key found is = " + titleKey.toString());
			System.out.println();

			System.out.println("Descrambling started.");

			CSSDescrambler cssDescrambler = new CSSDescrambler(titleKey, inputFile, new File(args[1]));

			long startTime = System.currentTimeMillis();

			cssDescrambler.descramble();

			long endTime = System.currentTimeMillis();

			System.out.println("Descrambling finished.  Elapsed time = " + ((endTime - startTime) / 1000) + " sec");
		}
		else
			System.out.println("\n Unable to descramble file because title key attack failed");
	}
}
