/* CreateSkeleton
 * Creates a skeleton PlayStation dev project based on the output of SymDumpTE
 * or another source of the appropriate JSON data
 * Copyright 2019 Ben Lincoln
 * https://www.beneaththewaves.net/
 * 
 * This file is part of CreateSkeleton.
 * 
 * CreateSkeleton is free software: you can redistribute it and/or modify
 * it under the terms of version 3 of the GNU General Public License as published by
 * the Free Software Foundation.
 * 
 * CreateSkeleton is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with CreateSkeleton (in the file LICENSE.txt).  
 * If not, see <http://www.gnu.org/licenses/>.
 */
//%PROJECT_NAME%: Machine-generated decompilation-exporting script
// Modified from Decompile.java, included with Ghidra

import java.io.*;
import java.util.*;

import ghidra.app.decompiler.*;
import ghidra.app.plugin.core.script.Ingredient;
import ghidra.app.plugin.core.script.IngredientDescription;
import ghidra.app.script.GatherParamPanel;
import ghidra.app.script.GhidraScript;
import ghidra.app.util.Option;
import ghidra.app.util.exporter.CppExporter;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterManager;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.*;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.*;

import utilities.util.FileUtilities;

public class %PROJECT_NAME%TDRDecompile extends GhidraScript implements Ingredient {

	@Override
	public void run() throws Exception {
		IngredientDescription[] ingredients = getIngredientDescriptions();
		for (IngredientDescription ingredient : ingredients) {
			state.addParameter(ingredient.getID(), ingredient.getLabel(), ingredient.getType(),
				ingredient.getDefaultValue());
		}
		if (!state.displayParameterGatherer("Script Options")) {
			return;
		}
		File outputFile = (File) state.getEnvironmentVar("COutputFile");
		/* CppExporter cppExporter = new CppExporter();
		List<Option> options = new ArrayList<Option>();
		options.add(new Option(CppExporter.CREATE_HEADER_FILE, new Boolean(true)));
		cppExporter.setOptions(options);
		cppExporter.setExporterServiceProvider(state.getTool());
		cppExporter.export(outputFile, currentProgram, null, monitor); */
		
		DecompileOptions options = new DecompileOptions();
		DecompInterface ifc = new DecompInterface();
		ifc.setOptions(options);
        
		if ( !ifc.openProgram(this.currentProgram) ) {
			throw new DecompileException("Decompiler", "Unable to initialize: "+ifc.getLastMessage());
		}
		/* ifc.setSimplificationStyle("normalize"); */
		FunctionIterator funcIter = currentProgram.getFunctionManager().getFunctions(true);
		
		FileWriter output = new FileWriter(outputFile);
		BufferedWriter writer = new BufferedWriter(output);
		
		while (funcIter.hasNext() && !monitor.isCancelled())
		{
			String currentTaskDescription = "";
			try
			{
				String functionName = "";
				String decompiledCode = "";
				String funcSig = "";
				boolean gotCode = true;
				currentTaskDescription = "while getting next function";
				Function func = funcIter.next();
				currentTaskDescription = "getting function name";
				functionName = func.getName();
				currentTaskDescription = "getting function entrypoint for " + functionName;
				Address funcAddress = func.getEntryPoint();
				currentTaskDescription = "getting function entrypoint offset for " + functionName;
				long funcOffset = funcAddress.getOffset();
				currentTaskDescription = "getting function " + functionName + " entrypoint offset " + String.valueOf(funcOffset) + " as hex string";
				String formattedOffset = Long.toHexString(funcOffset);
				currentTaskDescription = "getting DecompileResults for function " + functionName + " at " + formattedOffset;
				DecompileResults res = ifc.decompileFunction(func, 0, null);
				currentTaskDescription = "getting DecompiledFunction for function " + functionName + " at " + formattedOffset;
				DecompiledFunction df = res.getDecompiledFunction();
				currentTaskDescription = "getting C code for function " + functionName + " at " + formattedOffset;
				try
				{
					decompiledCode = df.getC();
				}
				catch (Exception e)
				{
					HandleDecompileException(e, currentTaskDescription);
					decompiledCode = "// " + e.toString() + " thrown " + currentTaskDescription + ": " + e.getMessage();
					gotCode = false;
				}
				if (gotCode)
				{
					currentTaskDescription = "getting signature for function at " + formattedOffset;
					try
					{
						funcSig = df.getSignature();
					}
					catch (Exception e)
					{
						HandleDecompileException(e, currentTaskDescription);
						funcSig = "// " + e.toString() + " thrown " + currentTaskDescription + ": " + e.getMessage();
						gotCode = false;
					}
				}
				currentTaskDescription = "writing function at " + formattedOffset + " to output file";
				if (gotCode)
				{
					writer.write("// TDR: BEGIN FUNCTION");
					writer.newLine();
					writer.write("// TDR: Address: 0x" + formattedOffset);
					writer.newLine();
					writer.write("// TDR: Name: " + functionName);
					writer.newLine();
					writer.write("// TDR: Signature: " + funcSig);
					writer.newLine();
					//writer.newLine();
				}
				writer.write(decompiledCode);
				writer.newLine();
				if (gotCode)
				{
					writer.write("// TDR: END FUNCTION");
					writer.newLine();
					//writer.newLine();
				}
			}
			catch (Exception e)
			{
				HandleDecompileException(e, currentTaskDescription);
			}
		}
		
		writer.close();
	}
	
	public void HandleDecompileException(Exception e, String taskDescription)
	{
		println(e.toString() + " thrown " + taskDescription + ": " + e.getMessage());
	}

	@Override
	public IngredientDescription[] getIngredientDescriptions() {
		IngredientDescription[] retVal = new IngredientDescription[] {
			new IngredientDescription("COutputFile", "Output C File", GatherParamPanel.FILE, "%DECOMPILED_C_CODE_FILE%") };
		return retVal;
	}

}
