@SuppressWarnings("rawtypes")
public class CrococryptFile implements CbIEncrypt, CbIDecrypt, SimpleCallback{
	static{
		UICenter.INIT();
		InitCrypto.INIT();
	}
	
	public static void main(String[] args) throws Exception{
		if(args == null || args.length == 0){
			launch(null);
		}
		else{
			launch(ConsoleOptions.parse(args));
		}
	}
	
	public static final void launch(CrococryptParameters params){
		boolean jceok = true;
		try {
			if(Cipher.getMaxAllowedKeyLength("AES") < 256) jceok = false;
		} catch (Exception e) {
			jceok = false;
			e.printStackTrace();
		}
		if(!jceok){
			CPrint.error(_T.Launcher_jcepolicyerr);
			if(UICenter.isGUI()){
				PageLauncher.launch(new JCEPolicyError());
			}
			return;
		}
		
		if(System.console() != null && params == null)
			System.out.println("You can use the commandline interface by starting with -c");
		
		try{
			new CrococryptFile(params).main();
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	

	public static String LASTERROR = null;
	
	private boolean check_forcedec = true;
	private boolean check_forcedeccloak = true;
	private boolean check_forceparams = true;
	private CrococryptParameters inputparams = null;
	private HashMap<SuitePARAM, Object> operationParams = new HashMap<>();
	private SuitePARAM[] reqparams = null;
	private int reqparams_index = 0;
	private Object currentSource;
	
	public CrococryptFile(CrococryptParameters params){
		this.inputparams = params;
		
		if(params != null && params.forceconsole)
			UICenter.setCUI();
	}
	
	private enum STATE{
		none,

		loadparams,
		cloak4dec,
		dec4enc,
		operationparam,
		decrypt,
		decryptfinishoperation,
		encrypt,
		encryptfinishoperation,
	}
	
	private STATE cur = STATE.none;
	private SuccessCallback successcb = new SuccessCallback() {
		@Override public void callbackValue(Object source, SUCCESS ret) {
			CrococryptFile.this.callbackValue(source, ret);
		}
	};

	@SuppressWarnings("unchecked")
	public void main(){
		if(UICenter.isCUI() && (inputparams == null || inputparams.destination == null || inputparams.filesanddirs == null || inputparams.filesanddirs.size() == 0)){
			CPrint.line("Invalid or insufficient options. Possible commandline options are:\n");
			CPrint.line("java -jar crococryptfile.jar [OPTIONS] [files & directories]");
			for(ConsoleOptions_Params param: ConsoleOptions_Params.values())
				CPrint.line(" -" + param.name() + ConsoleOptions.description.get(param));
			CPrint.line(" files & dirs\tSingle .croco-archive (decrypt)|files and directories (encrypt)");
			CPrint.line("\nReleased under the GPLv3 by Frank Hissen (www.frankhissen.de)");
			return;
		}
		
		if(check_forceparams && (inputparams == null || inputparams.filesanddirs == null || inputparams.filesanddirs.size() == 0)){
			check_forceparams = false;

			if(UICenter.isGUI()){
				cur = STATE.loadparams;
				if(inputparams != null && inputparams.cloakedfile)
					CrocoparamsDialog.requestParams(this, inputparams, _T.FileSelection_title4cloak.val());
				else
					CrocoparamsDialog.requestParams(this, inputparams);
			}
			else{
				UICenter.message("Invalid or insufficient options");
			}
		}
		else if(check_forcedec && !inputparams.decmode && inputparams.filesanddirs.size() == 1 && inputparams.filesanddirs.get(0).isFile()
				&& inputparams.filesanddirs.get(0).getName().toLowerCase().endsWith(SUITES.FILEEXTENSIONwDOT)){
			check_forcedec = false;

			if(UICenter.isGUI()){
				cur = STATE.dec4enc;
				SimpleDialogs.questionYesNo(_T.FileSelection_dec4enc.val(), successcb);
			}
			else{
				main();
			}
		}
		else if(check_forcedeccloak && !inputparams.cloakedfile && !inputparams.decmode && inputparams.filesanddirs.size() == 1 && inputparams.filesanddirs.get(0).isFile()
				&& inputparams.filesanddirs.get(0).getName().indexOf(".") < 0){
			check_forcedeccloak = false;

			if(UICenter.isGUI()){
				cur = STATE.cloak4dec;
				SimpleDialogs.questionYesNo(_T.FileSelection_dec4cloak.val(), successcb);
			}
			else{
				main();
			}
		}
		else{
			if(inputparams.decmode){
				if(UICenter.isGUI()){
					File startDest = inputparams.destination;
					if(startDest == null){
						File file = inputparams.filesanddirs.get(0);
						String tmp = file.getName();
						if(tmp.indexOf(".") > 0) tmp = tmp.substring(0, tmp.lastIndexOf("."));
						else tmp = tmp + " - archive";
						startDest = new File(file.getParentFile(), tmp);
						if(startDest.exists() && startDest.isFile()) startDest = new File(file.getParentFile(), tmp + " - archive");
					}

					Decrypt dec = new Decrypt(this);
					dec.setFolder(startDest);
					PageLauncher.launch(dec);
				}
				else{
					try {
						if(inputparams.cloakedfile){
							inputparams.suite = SUITES.PBECLOAKED_AESTWO;
						}
						else{
							File croco = inputparams.filesanddirs.get(0);
							inputparams.suite = SUITES.read(croco);
						}
						if(inputparams.suite == null){
							UICenter.message("Input parameters are invalid (no valid provider or file)");
						}
						else{
							prepareDecryptParams(inputparams.suite);
							setRawParams(inputparams.rawcreds);
							retrieveOperationParams();
						}
					} catch (Exception e) {
						e.printStackTrace();
						UICenter.message("Input parameters are invalid");
					}
				}
			}
			else{
				if(UICenter.isGUI()){
					File startDest = inputparams.destination;
					if(startDest == null && inputparams.singlesource){
						startDest = inputparams.filesanddirs.get(0);
						if(OSFolders.isRoot(startDest)) startDest = new File(startDest, _T.EncryptWindow_EncryptedFile + BasicFileinfo.FILEEXTENSIONwDOT);
						else if(OSFolders.isRoot(startDest.getParentFile())) startDest = new File(startDest.getParentFile(), _T.EncryptWindow_EncryptedFile + BasicFileinfo.FILEEXTENSIONwDOT);
						else startDest = new File(startDest.getParentFile(), startDest.getParentFile().getName() + BasicFileinfo.FILEEXTENSIONwDOT);
					}
					Encrypt enc = new Encrypt(this, inputparams, startDest);
					PageLauncher.launch(enc);
				}
				else{
					if(inputparams.suite != null){
						prepareEncryptParams(inputparams.suite);
						setRawParams(inputparams.rawcreds);
						retrieveOperationParams();
					}
					else{
						UICenter.message("No provider specified, valid providers are: " + SUITES.providerListAsLine());
					}
				}
			}
		}
	}
	
	
	@Override
	public void callbackValue(Object source, Object ret) {
		try {
			switch (cur) {
			case loadparams:
				inputparams = (CrococryptParameters) ret;
				cur = STATE.none;
				main();
				break;
				
			case cloak4dec:
				if(ret == SUCCESS.TRUE) inputparams.cloakedfile = true;
			case dec4enc:
				if(ret == SUCCESS.TRUE)
					inputparams.decmode = true;
				else
					inputparams.decmode = false;
				cur = STATE.none;
				main();
				break;
				
			case decrypt:
				cur = STATE.none;
				SUCCESS decsucc = (SUCCESS) ret;
				
				switch (decsucc) {
				case TRUE:
					if(UICenter.isGUI()){
						cur = STATE.decryptfinishoperation;
						SimpleDialogs.message(_T.DecryptWindow_success, successcb);
					}
					else{
						UICenter.message(_T.DecryptWindow_success);
					}
					Page.exitPage(currentSource);

					break;

				case FALSE:
					if(LASTERROR != null){
						UICenter.message(_T.DecryptWindow_failedspecific + "\n" + LASTERROR);
						LASTERROR = null;
					}
					else{
						UICenter.message(_T.DecryptWindow_failedgeneral);
					}
					break;

				case CANCEL:
					UICenter.message(_T.DecryptWindow_cancel);
					Page.exitPage(currentSource);

					break;
				}
				
				break;
				
			case decryptfinishoperation:
				try {
					Desktop.getDesktop().browse(inputparams.destination.toURI());
				} catch (Exception e2) {}
				break;
				
			case encrypt:
				cur = STATE.none;
				SUCCESS encsucc = (SUCCESS) ret;
				
				switch (encsucc) {
				case TRUE:
					UICenter.message(_T.EncryptWindow_success);
					break;

				case FALSE:
					if(LASTERROR != null){
						UICenter.message(_T.EncryptWindow_failedspecific + "\n" + LASTERROR);
						LASTERROR = null;
					}
					else{
						UICenter.message(_T.EncryptWindow_failedgeneral);
					}
					break;

				case CANCEL:
					UICenter.message(_T.EncryptWindow_cancel);
					break;
				}
				
				Page.exitPage(currentSource);

				break;
				
			case encryptfinishoperation:
				break;
				
			case operationparam:
				setOperationParam(source, ret);
				retrieveOperationParams();
				break;

			default:
				UICenter.message("Internal error: unknown callback response - " + cur);
			}
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(-98);
		}
	}

	
	@Override
	public void callbackDecrypt(Object source, File destinationFolder) {
		currentSource = source;
		inputparams.destination = destinationFolder;
		File croco = inputparams.filesanddirs.get(0);
		
		if(!inputparams.cloakedfile){
			inputparams.suite = SUITES.read(croco);
			if(inputparams.suite == null){
				UICenter.message(_T.DecryptWindow_unknownfile);
				return;
			}
		}
		else{
			inputparams.suite = SUITES.PBECLOAKED_AESTWO;
		}
		
		prepareDecryptParams(inputparams.suite);
		retrieveOperationParams();
	}

	@Override
	public void callbackEncrypt(Object source, File destination, SUITES provider) {
		if(provider == null){
			UICenter.message("Unknown Operation");
			return;
		}
		
		currentSource = source;
		inputparams.destination = destination;
		inputparams.suite = provider;
		
		prepareEncryptParams(provider);
		retrieveOperationParams();
	}
	
	@Override
	public boolean isDecryptRunning(Object source) {
		if(source == currentSource && cur == STATE.decrypt)
			return true;
		return false;
	}
	
	@Override
	public boolean isEncryptRunning(Object source) {
		if(source == currentSource && cur == STATE.encrypt)
			return true;
		return false;
	}
	
	private void prepareEncryptParams(SUITES suite){
		reqparams = suite.getEncryptParameters();
		reqparams_index = 0;
		operationParams.clear();
	}
	
	private void prepareDecryptParams(SUITES suite){
		reqparams = suite.getDecryptParameters();
		reqparams_index = 0;
		operationParams.clear();
	}
	
	
	@SuppressWarnings("unchecked")
	private void retrieveOperationParams(){
		if(reqparams == null || reqparams.length <= reqparams_index){
			if(inputparams.decmode){
				cur = STATE.decrypt;
				doDecrypt();
			}
			else{
				cur = STATE.encrypt;
				doEncrypt();
			}
		}
		else{
			cur = STATE.operationparam;
			SuitePARAM theparam = reqparams[reqparams_index];
			reqparams_index++;
			
			if(inputparams.decmode){
				switch (theparam) {
				case password:
					if(UICenter.isGUI())
						new PasswordInputdialog(currentSource).main(this);
					else
						new PasswordInputConsole().main(this);
					break;
					
				case pgp_dec:
					if(UICenter.isGUI())
						PGP_PrivatekeyDialog.requestParams(currentSource, this);
					break;

				default:
					UICenter.message("Internal error: unknown parameter");
				}
			}
			else{
				switch (theparam) {
				case password:
					if(UICenter.isGUI())
						
						new PasswordCreatedialogWithOptions(currentSource).main(this);
					else
						new PasswordCreateConsole().main(this);
					break;
					
				case capi_alias:
					if(UICenter.isGUI()){
						CAPI_DNList dnlist = new CAPI_DNList(this);
						PageLauncher.launch(new Options(currentSource), dnlist);
					}
					break;
					
				case pgp_enc:
					if(UICenter.isGUI()){
						PageLauncher.launch(new Options(currentSource), new PGP_Keylist(this));
					}
					break;

				default:
					UICenter.message("Internal error: unknown parameter");
				}
			}
		}
	}
	
	
	@SuppressWarnings("unchecked")
	private void setOperationParam(Object source, Object value){
		if(source == null || value == null) return;
		
		Class c = null;
		if(source instanceof Class) c = (Class) source;
		else c = source.getClass();
		
		if(c == PasswordCreatedialog.class || c == PasswordCreatedialogWithOptions.class || c == PasswordCreateConsole.class
				|| c == PasswordInputdialog.class || c == PasswordInputConsole.class){
			if(value instanceof HashMap){
				HashMap hm = (HashMap) value;
				if(hm.isEmpty()) return;
				
				if(hm.keySet().iterator().next() instanceof SuitePARAM){
					operationParams.putAll(hm);
				}
			}
			else{
				operationParams.put(SuitePARAM.password, value);
			}
		}
		else if(c == CAPI_DNList.class){
			operationParams.put(SuitePARAM.capi_alias, value);
		}
		else if(value instanceof HashMap){
			HashMap hm = (HashMap) value;
			if(hm.isEmpty()) return;
			
			if(hm.keySet().iterator().next() instanceof SuitePARAM){
				operationParams.putAll(hm);
			}
		}
	}
	
	private void setRawParams(ArrayList<String> raw){
		if(reqparams == null || reqparams.length == 0 || raw == null || raw.size() < reqparams.length) return;

		for(reqparams_index = 0; reqparams_index < reqparams.length; reqparams_index++){
			SuitePARAM tmp = reqparams[reqparams_index];
			switch (tmp) {
			case capi_alias:
				operationParams.put(tmp, raw.get(reqparams_index));
				continue;

			case password:
				UICenter.message("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +
						"You are providing a password as commandline option, this is NOT RECOMMEND" +
						"\nsince your password might be easily gathered." +
						"\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
				char[] pw = raw.get(reqparams_index).toCharArray();
				if(pw.length < CryptoCodes.STANDARD_PBKDF2_PWLEN){
					CPrint.line(_T.PasswordEncrypt_len.msg(CryptoCodes.STANDARD_PBKDF2_PWLEN));
					continue;
				}
				operationParams.put(tmp, pw);
				continue;
				
			case pgp_enc:
				if(raw.size() < 2) {
					System.err.println("Not enough parameters specified");
					return;
				}
				operationParams.put(SuitePARAM.pgp_pubkeyfile, raw.get(0));
				
				ArrayList<Long> kids = new ArrayList<>();
				for(int i=1; i<raw.size(); i++){
					String rawid = raw.get(i).trim();
					if(rawid.length() < 16){
						System.err.println("Only long keyid format supported");
						continue;
					}

					Long l = 0L;
					if(rawid.length() == 16){
						try {
							l = ByteUtils.bytesToLong(Hex.decode(rawid));
						} catch (Exception e) {}
					}
					else{
						try {
							l = Long.parseLong(rawid);
						} catch (Exception e) {}
					}

					if(l != 0) {
						kids.add(l);
					}
					else{
						System.err.println("keyid " + rawid + " could not be read");
					}
				}
				if(kids.size() == 0) return;
				
				operationParams.put(SuitePARAM.pgp_keyidlist, kids);
				continue;

			case pgp_dec:
				if(raw.size() < 2) {
					System.err.println("Not enough parameters specified");
					return;
				}
				
				UICenter.message("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +
						"You are providing a password as commandline option, this is NOT RECOMMEND" +
						"\nsince your password might be easily gathered." +
						"\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
				char[] pwpgp = raw.get(1).toCharArray();

				operationParams.put(SuitePARAM.pgp_privkeyfile, raw.get(0));
				operationParams.put(SuitePARAM.password, pwpgp);
				continue;

			default:
				UICenter.message("unknown credential");
				break;
			}
		}
	}
	
	private void doEncrypt(){
		final StatusUpdate stat;
		if(UICenter.isGUI()){
			stat = new ProgressDoubleWindow(((Page)currentSource).getPageLauncher().getWindow()).prepare();
		}
		else if(inputparams.verboseconsole){
			stat = new StatusConsole();
		}
		else{
			stat = null;
		}

		if(stat != null){
			stat.start();
			stat.receiveMessageSummary(_T.Password_PBEinProgress.val());
		}
		
		Suite.getInitializedInstanceAsync(inputparams.suite, SuiteMODE.ENCRYPT, operationParams, stat, new SuiteReceiver() {
			@Override
			public void receiveInitializedInstance(Suite suite) {
				if(suite == null) {
					UICenter.message("Fatal error while initializing ENCRYPTION.");
					System.exit(-12);
				}
				
				StatusUpdate status = suite.getStatus();
				if(status == null || status.isActive())
					new CrocoFilewriter(successcb, suite, inputparams.filesanddirs.toArray(new File[]{}), inputparams.destination).execute(stat);
				else{
					try {
						if(successcb != null) successcb.callbackValue(this, SUCCESS.CANCEL);
						if(stat != null) stat.finished();
						if(suite != null) suite.deinit();
					} catch (Exception e) {}
				}
			}
		});
	}
	
	private void doDecrypt(){
		final StatusUpdate stat;
		if(UICenter.isGUI()){
			stat = new ProgressWindow(((Page)currentSource).getPageLauncher().getWindow()).prepare();
		}
		else if(inputparams.verboseconsole){
			stat = new StatusConsole();
		}
		else{
			stat = null;
		}

		if(stat != null){
			stat.start();
			stat.receiveMessageSummary(_T.Password_PBEinProgress.val());
		}

		Suite.getInitializedInstanceAsync(inputparams.suite, SuiteMODE.DECRYPT, operationParams, stat, new SuiteReceiver() {
			@Override
			public void receiveInitializedInstance(Suite suite) {
				if(suite == null) {
					UICenter.message("Fatal error while initializing DECRYPTION.");
					System.exit(-12);
				}

				StatusUpdate status = suite.getStatus();
				if(status == null || status.isActive())
					new CrocoFilereader(successcb, suite, inputparams.filesanddirs.get(0), inputparams.destination).execute(stat);
				else{
					try {
						if(successcb != null) successcb.callbackValue(this, SUCCESS.CANCEL);
						if(stat != null) stat.finished();
						if(suite != null) suite.deinit();
					} catch (Exception e) {}
				}
			}
		});
	}
}
 public enum SUITES implements BasicFileinfo{
	PBE1_AES(BASENUMBER + 10),
	PBE1_TWOFISH(BASENUMBER + 13),
	PBE1_SERPENT(BASENUMBER + 14),
	PBE1_CAMELLIA(BASENUMBER + 15),
	CAPI_RSAAES(BASENUMBER + 20),
	PGP_AES(BASENUMBER + 30),
	PBECLOAKED_AESTWO(BASENUMBER + 1000),
	PBECLOAKED1MB_AESTWO(BASENUMBER + 1001),

	;
	
	private int magicnumber;
	private ArrayList<OS> osspec = null;
	private SuitePARAM[] encparams, decparams;
	private SUITES(int magicnumber){
		this.magicnumber = magicnumber;
	}
	
	protected final void setOS(OS os){
		setOS(new OS[]{os});
	}

	protected final void setOS(OS[] oss){
		if(oss != null && oss.length > 0){
			osspec = new ArrayList<>();
			Collections.addAll(osspec, oss);
		}
	}
	
	protected final void setEncParams(SuitePARAM[] params){
		encparams = params;
	}
	
	protected final void setDecParams(SuitePARAM[] params){
		decparams = params;
	}
	
	public final SuitePARAM[] getEncryptParameters(){
		return encparams;
	}
	
	public final SuitePARAM[] getDecryptParameters(){
		return decparams;
	}
	
	public final boolean hasEncryptParameters(){
		return encparams != null && encparams.length > 0;
	}
	
	public final boolean hasDecryptParameters(){
		return decparams != null && decparams.length > 0;
	}
	
	public final int magicNumber(){
		return magicnumber;
	}
	
	public final boolean isAvailable(){
		if(osspec == null || osspec.contains(OSDetector.which())) return true;
		return false;
	}

	
	@SuppressWarnings("rawtypes")
	public static final ArrayList<Class> headers = new ArrayList<>();
	public static final ArrayList<Integer> numbers = new ArrayList<>();
	public static final ArrayList<String> descriptor = new ArrayList<>();
	public static final HashMap<Integer, SUITES> id = new HashMap<>();

	static{
		add(PBE1AESMain.class, PBE1_AES);
		add(PBE1TwofishMain.class, PBE1_TWOFISH);
		add(PBE1SerpentMain.class, PBE1_SERPENT);
		add(PBE1CamelliaMain.class, PBE1_CAMELLIA);
		add(CAPIRSAAESMain.class, CAPI_RSAAES);
		add(PGPAESMain.class, PGP_AES);
		add(PBECloaked_AES2F_Main.class, PBECLOAKED_AESTWO);
		add(PBECloaked_AES2F_Main_1MB.class, PBECLOAKED1MB_AESTWO);

		
				SUITES[] passworders = {
				PBE1_AES,
				PBE1_TWOFISH,
				PBE1_SERPENT,
				PBE1_CAMELLIA,
				PBECLOAKED_AESTWO,
				PBECLOAKED1MB_AESTWO,
		};
		
		for(SUITES s: passworders){
			s.setEncParams(new SuitePARAM[]{SuitePARAM.password});
			s.setDecParams(new SuitePARAM[]{SuitePARAM.password});
		}
		
		CAPI_RSAAES.setOS(OS.WIN);
		CAPI_RSAAES.setEncParams(new SuitePARAM[]{SuitePARAM.capi_alias});
		
		PGP_AES.setEncParams(new SuitePARAM[]{SuitePARAM.pgp_enc});
		PGP_AES.setDecParams(new SuitePARAM[]{SuitePARAM.pgp_dec});
		
		
		HashSet<Integer> nos = new HashSet<>();
		for(SUITES s: SUITES.values()){
			if(nos.contains(s.magicNumber()))
				System.err.println("WARNING: Same magic number used twice!");
			else
				nos.add(s.magicNumber());
		}
	}
	
	private static final void add(@SuppressWarnings("rawtypes") Class suiteClass, SUITES s){
		headers.add(suiteClass);
		numbers.add(s.magicNumber());
		descriptor.add(_T.valueOf("Suite_" + s.name()).val());
		id.put(s.magicNumber(), s);
	}
	
	public static final int numberFromClass(Suite header){
		int no = headers.indexOf(header.getClass());
		return numbers.get(no);
	}

	@SuppressWarnings("rawtypes")
	public static final Class classFromSuites(SUITES suite){
		int no = numbers.indexOf(suite.magicNumber());
		return headers.get(no);
	}

	
	public static void create(OutputStream out, int magicnumber) throws IOException{
		StreamMachine.write(out, magicnumber);
	}
	
	public static SUITES read(File f){
		try {
			return readNclose(new FileInputStream(f));
		} catch (Exception e) {
			return null;
		}
	}
	
	public static SUITES readNclose(InputStream is){
		SUITES suite = null;
		suite = read(is);

		try {
			is.close();
		} catch (Exception e) {}
		
		return suite;
	}

	public static SUITES read(InputStream is){
		int i = -1;
		i = StreamMachine.readO(is, i);
		return id.get(i);
	}
	
	private static String providerline;
	public static String providerListAsLine(){
		if(providerline == null){
			providerline = "";
			for(SUITES sx: SUITES.values())
				providerline += sx.name() + " ";
			providerline = providerline.trim();
		}
		return providerline;
	}
}
 public abstract class Suite {
	private SuiteMODE mode;
	private StatusUpdate status;
	private boolean initialized = false;
	
	protected Suite(){}
	
	public final void init(SuiteMODE mode, HashMap<SuitePARAM, Object> params, StatusUpdate status) throws IllegalArgumentException{
		if(mode == null) throw new IllegalArgumentException("no mode specified");
		
		this.status = status;
		_init(mode, params);
		
		this.mode = mode;
		initialized = true;
	}
	
	public SuiteMODE getMode(){
		return mode;
	}
	
	public final void writeTo(OutputStream out) throws IllegalStateException{
		if(!initialized) throw new IllegalStateException("not initialized");
		if(getMode() != SuiteMODE.ENCRYPT) throw new IllegalStateException("wrong mode");
		
		_writeTo(out);
	}
	
	public final void readFrom(InputStream is) throws IllegalStateException{
		if(!initialized) throw new IllegalStateException("not initialized");
		if(getMode() != SuiteMODE.DECRYPT) throw new IllegalStateException("wrong mode");
		
		_readFrom(is);
	}

	abstract protected void _init(SuiteMODE mode, HashMap<SuitePARAM, Object> params) throws IllegalArgumentException;
	abstract protected void _writeTo(OutputStream out) throws IllegalStateException;
	abstract protected void _readFrom(InputStream is) throws IllegalStateException;
	abstract public CipherMain getCipher();
	abstract public byte[] getAttributeIV();
	abstract public int headerLength();
	abstract public void deinit();
	
	public final byte[] getAlteredIV(int add){
		add = add % CryptoCodes.STANDARD_IVSIZE;
		byte[] buf = Arrays.copyOf(getAttributeIV(), CryptoCodes.STANDARD_IVSIZE);
		buf[add]++;
		return buf;
	}
	
	private int len = -1;
	public final int suiteLength(){
		if(len < 0){
			len = headerLength();
			if(!(this instanceof PBECloaked_AES2F_Main)) len += SUITES.MAGICNUMBER_LENGTH;
		}
		return len;
	}
	
	public final StatusUpdate getStatus(){
		return status;
	}
	
	
	public static final Suite getInstance(SUITES suite){
		try {
			return (Suite)(SUITES.classFromSuites(suite).newInstance());
		} catch (Exception e) {
			System.err.println("getInstance: " + e.getLocalizedMessage());
			return null;	
		}
	}

	public static final Suite getInitializedInstance(SUITES suite, SuiteMODE mode, HashMap<SuitePARAM, Object> params, StatusUpdate status){
		try {
			Suite instance = (Suite)(SUITES.classFromSuites(suite).newInstance());
			instance.init(mode, params, status);
			return instance;
		} catch (Exception e) {
			e.printStackTrace();
			System.err.println("getInitializedInstance: " + e.getLocalizedMessage());
			return null;	
		}
	}
	
	
	public static final void getInitializedInstanceAsync(final SUITES suite, final SuiteMODE mode,
			final HashMap<SuitePARAM, Object> params, final StatusUpdate status, final SuiteReceiver rec){
		if(rec == null){
			System.err.println("SuiteReceiver == null");
			return;
		}

		if(suite == null){
			System.err.println("Requested SUITES == null");
			rec.receiveInitializedInstance(null);
			return;
		}
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Suite instance = (Suite)(SUITES.classFromSuites(suite).newInstance());
					instance.init(mode, params, status);
					rec.receiveInitializedInstance(instance);
				} catch (Exception e) {
					e.printStackTrace();
					System.err.println("getInitializedInstanceAsync: " + e.getLocalizedMessage());
					rec.receiveInitializedInstance(null);
				}
			}
		}).start();
	}
	
	
	public static final Suite getInitializedInstance(SUITES suite, SuiteMODE mode, SuitePARAM aparam, Object avalue){
		HashMap<SuitePARAM, Object> params = new HashMap<>();
		params.put(aparam, avalue);
		return getInitializedInstance(suite, mode, params, null);
	}
	

	public interface SuiteReceiver{
		public void receiveInitializedInstance(Suite suite);
	}
}
 public class PBECloaked_AES2F_Main extends Suite {
	private byte[] ATTRIBUTE_SALT = new byte[CryptoCodes.STANDARD_SALTSIZE];
	private byte[] ATTRIBUTE_SALTENC = new byte[CryptoCodes.STANDARD_SALTSIZE];
	private byte[] ATTRIBUTE_KEY_AES = new byte[CryptoCodes.AES_KEYSIZE];
	private byte[] ATTRIBUTE_KEY_TWO = new byte[CryptoCodes.AES_KEYSIZE];
	private byte[] ATTRIBUTE_IV = new byte[CryptoCodes.STANDARD_IVSIZE];

	private PBECloaked_AES2F_KeySet key;
	private CipherMain ciph;
	private char[] pw;

	@Override
	public final void deinit(){
		if(key != null) key.deinit();
		key = null;
		CryptoUtils.kill(pw);
		CryptoUtils.kill(ATTRIBUTE_KEY_AES, ATTRIBUTE_KEY_TWO, ATTRIBUTE_IV);
		ciph.deinint();
		System.gc();
	}
	
	@Override
	protected void finalize() throws Throwable {
		deinit();
		super.finalize();
	}
	
	private final int len = ByteUtils.sizeInBytes(ATTRIBUTE_SALT, ATTRIBUTE_SALTENC, ATTRIBUTE_KEY_AES, ATTRIBUTE_KEY_TWO, ATTRIBUTE_IV);
	public final int headerLength(){
		return len;
	}
	
	public CipherMain getCipher(){
		return ciph;
	}

	@Override
	protected void _init(SuiteMODE mode, HashMap<SuitePARAM, Object> params) throws IllegalArgumentException{
		if(params == null || params.size() == 0)  throw new IllegalArgumentException("PBE: no params specified");
		pw = (char[])params.get(SuitePARAM.password);
		if(pw == null) throw new IllegalArgumentException("PBE must specify a password");
		
		int itcount_ext = 0;
		if(params.containsKey(SuitePARAM.itcount)){
			try {
				itcount_ext = Integer.parseInt((String)params.get(SuitePARAM.itcount));
			} catch (Exception e) {}
		}

		if(mode == SuiteMODE.ENCRYPT){
			key = PBECloaked_AES2F_PwToKeys.createPBE(pw, itcount_ext, getStatus());
			
			ciph = CipherMain.instance(CryptoUtils.toArray(BASECIPHER.TWOFISH, BASECIPHER.AES), key.two_plainkey, key.aes_plainkey);
			
			ATTRIBUTE_SALT = key.salt;
			ATTRIBUTE_SALTENC = ciph.doEnc_ECB(ATTRIBUTE_SALT);
			
			ATTRIBUTE_KEY_AES = key.aes_enckey;
			ATTRIBUTE_KEY_TWO = key.two_enckey;
			ATTRIBUTE_IV = CryptoUtils.randIv16();
		}
	}
	
	protected void _writeTo(OutputStream out) throws IllegalStateException{
		try {
			StreamMachine.write(out,
					ATTRIBUTE_SALT,
					ATTRIBUTE_SALTENC,
					ATTRIBUTE_KEY_AES,
					ATTRIBUTE_KEY_TWO,
					ciph.doEnc_ECB(ATTRIBUTE_IV)
					);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	protected void _readFrom(InputStream is) throws IllegalStateException{
		StreamMachine sm = new StreamMachine(is);

		sm.read(ATTRIBUTE_SALT);
		sm.read(ATTRIBUTE_SALTENC);
		sm.read(ATTRIBUTE_KEY_AES);
		sm.read(ATTRIBUTE_KEY_TWO);
		sm.read(ATTRIBUTE_IV);
		
		if(ciph == null){
			key = new PBECloaked_AES2F_KeySet();
			key.aes_enckey = ATTRIBUTE_KEY_AES;
			key.two_enckey = ATTRIBUTE_KEY_TWO;
			key.salt = ATTRIBUTE_SALT;
			
			PBECloaked_AES2F_PwToKeys.loadPBE(pw, key, ATTRIBUTE_SALT, ATTRIBUTE_SALTENC, getStatus());
			ciph = CipherMain.instance(CryptoUtils.toArray(BASECIPHER.TWOFISH, BASECIPHER.AES), key.two_plainkey, key.aes_plainkey);
		}
		
		ATTRIBUTE_IV = ciph.doDec_ECB(ATTRIBUTE_IV);
	}
	
	public byte[] getAttributeIV(){
		return ATTRIBUTE_IV;
	}
	
	public boolean fill(){
		return false;
	}
}
 public enum _T {
	CAPIDNListWindow_nokeys,
	CAPIDNListWindow_title,
	CAPI_DNnotfound,
	CrocoFilereader_already,
	CrocoFilereader_decrypting,
	CrocoFilewriter_encrypting,
	DecryptWindow_already,
	DecryptWindow_cancel,
	DecryptWindow_destination,
	DecryptWindow_failedgeneral,
	DecryptWindow_failedspecific,
	DecryptWindow_folderisfile,
	DecryptWindow_folderreadonly,
	DecryptWindow_invalid,
	DecryptWindow_nofile,
	DecryptWindow_success,
	DecryptWindow_text,
	DecryptWindow_title,
	DecryptWindow_unknownfile,
	DecryptWindow_wrongversion,
	Decrypt_Start,
	EncryptListChoose,
	EncryptListChooseMulti,
	EncryptWindow_EncryptedFile,
	EncryptWindow_Multidir,
	EncryptWindow_Multifile,
	EncryptWindow_MultifileMultidir,
	EncryptWindow_MultifileSingledir,
	EncryptWindow_Singledir,
	EncryptWindow_Singlefile,
	EncryptWindow_SinglefileMultidir,
	EncryptWindow_SinglefileSingledir,
	EncryptWindow_already,
	EncryptWindow_alreadydir,
	EncryptWindow_cancel,
	EncryptWindow_createdir,
	EncryptWindow_destination,
	EncryptWindow_failedgeneral,
	EncryptWindow_failedspecific,
	EncryptWindow_folderreadonly,
	EncryptWindow_multisources,
	EncryptWindow_success,
	EncryptWindow_title,
	FileSelection_dec4cloak,
	FileSelection_dec4enc,
	FileSelection_selectbutton,
	FileSelection_title,
	FileSelection_title4cloak,
	FilepathFailure,
	General_cancel,
	General_done,
	General_empty,
	General_none,
	General_open,
	General_password,
	General_quit,
	General_synchronize,
	JCEPolicyError_title,
	JCEPolicyError_text,
	Launcher_jcepolicyerr,
	Launcher_reallyquit,
	PasswordDecrypt_title,
	PasswordEncrypt_label,
	PasswordEncrypt_len,
	PasswordEncrypt_nomatch,
	PasswordEncrypt_retype,
	PasswordEncrypt_itcount,
	PasswordEncrypt_itcountdescr,
	PasswordEncrypt_title,
	Password_caps,
	Password_PBEinProgress,
	PGP_errorNokey,
	PGP_errorSeckeyfailed,
	PGP_nokeyfile,
	PGP_novalidprivkey,
	PGP_privkey_wrongfile,
	PGP_privkeypasstitle,
	PGP_selprivkey,
	PGP_selprivkeyTitle,
	PGP_selpubkey,
	PBKDF2Interstep_start,
	PBKDF2Interstep_found,
	PBKDF2Interstep_steplong,
	Suite_CAPI_RSAAES,
	Suite_PBE1_AES,
	Suite_PBE1_TWOFISH,
	Suite_PBE1_SERPENT,
	Suite_PBE1_CAMELLIA,
	Suite_PGP_AES,
	Suite_PBECLOAKED_AESTWO,
	Suite_PBECLOAKED1MB_AESTWO,

	;
	
	
	private static ResourceBundle rb;
	static{
		try {
			rb = ResourceBundle.getBundle(ResourceCenter.getTexts() + "text", ResourceCenter.getLocale());
		} catch (Exception e) {
			rb = ResourceBundle.getBundle(ResourceCenter.getTexts() + "text", Locale.ENGLISH);
		}
	}
	
	
	public String val(){
		return rb.getString(name());
	}
	
	public String msg(Object... obj){
		return MessageFormat.format(toString(), obj);
	}
	
	@Override
	public String toString() {
		return val();
	};
}
 public enum SUCCESS {
	TRUE,
	FALSE,
	CANCEL,
}
 public abstract class SuccessCallback implements SimpleCallback<SUCCESS> {
	@Override
	public abstract void callbackValue(Object source, SUCCESS ret);
}
 public class CryptoUtils {
	static{
		InitCrypto.INIT();
	}
	
	private static Random randio = new Random();
	static{
		randio.nextBoolean();
		long l = randio.nextLong();
		randio.setSeed(System.currentTimeMillis() ^ l);
	}

	public static final void kill(byte[] b){
		if(b == null) return;
		randio.nextBytes(b);
	}

	public static final void kill(byte[]... bs){
		if(bs == null) return;
		
		for(byte[]b: bs)
			kill(b);
	}

	public static final void kill(char[] b){
		if(b == null) return;
		for(int i=0; i<b.length; i++)
			b[i] = (char)randio.nextInt();
	}
	

	private static final SecureRandom sr = new KeygenUtils().makeSR();
	public static final byte[] randIv16(){
		byte[] ret = new byte[16];
		sr.nextBytes(ret);
		return ret;
	}

	public static final byte[] randIv(int size){
		byte[] ret = new byte[size];
		sr.nextBytes(ret);
		return ret;
	}
	
	
	public static final long makeSimpleId(){
		return UUID.randomUUID().getMostSignificantBits();
	}
	
	
	public static final String[] toArray(String... s){
		return s;
	}

	public static final BASECIPHER[] toArray(BASECIPHER... bc){
		return bc;
	}

	public static final FSecretKeySpec[] toArray(FSecretKeySpec... fs){
		return fs;
	}

	public static final FSecretKeySpec[] toArray(byte[]... rawkeys){
		if(rawkeys == null || rawkeys.length == 0) return null;
		
		FSecretKeySpec[] fs = new FSecretKeySpec[rawkeys.length];
		
		for(int i=0; i<rawkeys.length; i++){
			fs[i] = new FSecretKeySpec(rawkeys[i]);
		}
		
		return fs;
	}
}
 public class InitCrypto {
	private static boolean initialized = false;
	
	public static final void INIT(){
		if(initialized) return;
		
		initialized = true;
		BouncyCastleProvider bc = new BouncyCastleProvider();
		Security.addProvider(bc);
	}

	static{
		INIT();
	}
}
 public interface StatusUpdate {
	public void start();
	public void finished();
	public boolean isActive();
	public void receiveMessageSummary(String msg);
	public void receiveMessageDetails(String msg);
	public void receiveProgress(int perc);
	public void receiveDetailsProgress(int perc);
}

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import org.crococryptfile.CrococryptFile;
import org.crococryptfile.suites.SUITES;
import org.crococryptfile.suites.Suite;
import org.crococryptfile.suites.pbecloakedaes2f.PBECloaked_AES2F_Main;
import org.crococryptfile.ui.resources._T;
import org.fhissen.callbacks.SUCCESS;
import org.fhissen.callbacks.SuccessCallback;
import org.fhissen.crypto.CryptoUtils;
import org.fhissen.crypto.InitCrypto;
import org.fhissen.utils.ui.StatusUpdate;

package org.crococryptfile.datafile;



public class CrocoFilewriter {
	static{
		InitCrypto.INIT();
	}
	
	private SuccessCallback cb;

	private Suite suite;
