/* 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 memory-map generator and label-applying script

import ghidra.app.script.GhidraScript;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.util.importer.MemoryConflictHandler;
import ghidra.app.util.MemoryBlockUtil;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.flatapi.FlatProgramAPI;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.*;
import ghidra.program.model.block.CodeBlock;
import ghidra.program.model.block.PartitionCodeSubModel;
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.lang.RegisterValue;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.*;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.*;
import ghidra.util.exception.InvalidInputException;

import java.io.InputStream;
import java.io.StringWriter;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.util.HashMap; 
import java.util.*;

// Ben Lincoln, 2019

public class %PROJECT_NAME%TDRMapMemoryAndCreateLabels extends GhidraScript
{
	public class FunctionParameter
	{
		public String ParameterName;
		public DataType ParameterDataType;
		public String RegisterName;
		public int StackOffset;
		public boolean IsRegisterParam;
		
		public FunctionParameter(String name, DataType dataType, String registerName, int stackOffset, boolean isRegisterParam)
		{
			ParameterName = name;
			ParameterDataType = dataType;
			RegisterName = registerName;
			StackOffset = stackOffset;
			IsRegisterParam = isRegisterParam;
		}
	}

	private int numTried;
	private int numCreated;
	SymbolTable symbolTable;

	@Override
	public void run() throws Exception
	{
		// set stack pointer and global pointer
		ProgramContext ctx = currentProgram.getProgramContext();
		SetRegisterAtAddress(ctx, "sp", %ENTRYPOINT_ADDRESS%, %STACK_POINTER_VALUE%L);
		SetRegisterAtAddress(ctx, "gp", %ENTRYPOINT_ADDRESS%, %GLOBAL_POINTER_VALUE%L);

		// Define any additional memory regions, e.g. PlayStation RAM
%MEMORY_MAP_POPULATION%

		// get most data types
		HashMap<String, DataType> dataTypeMap = GetDataTypeMap();
		
		// Define labels (begin)
%LABEL_CREATION_SUBROUTINE_CALLS%

		// Define labels (end)
	}

	protected HashMap<String, DataType> GetDataTypeMap()
	{
		HashMap<String, DataType> dataTypeMap = new HashMap<>();

		// C primitives
		dataTypeMap.put("bool", new BooleanDataType());
		dataTypeMap.put("double", new DoubleDataType());
		dataTypeMap.put("char", new CharDataType());
		dataTypeMap.put("dword", new DWordDataType());
		dataTypeMap.put("float", new FloatDataType());
		dataTypeMap.put("int", new IntegerDataType());
		dataTypeMap.put("long", new LongDataType());
		dataTypeMap.put("longlong", new LongLongDataType());
		dataTypeMap.put("long long", new LongLongDataType());
		dataTypeMap.put("qword", new QWordDataType());
		dataTypeMap.put("short", new ShortDataType());
		dataTypeMap.put("string", new StringDataType());
		dataTypeMap.put("uchar", new UnsignedCharDataType());
		dataTypeMap.put("uint", new UnsignedIntegerDataType());
		dataTypeMap.put("ulong", new UnsignedLongDataType());
		dataTypeMap.put("ulonglong", new UnsignedLongLongDataType());
		dataTypeMap.put("unsigned char", new UnsignedCharDataType());
		dataTypeMap.put("unsigned int", new UnsignedIntegerDataType());
		dataTypeMap.put("unsigned long", new UnsignedLongDataType());
		dataTypeMap.put("unsigned longlong", new UnsignedLongLongDataType());
		dataTypeMap.put("unsigned long long", new UnsignedLongLongDataType());
		dataTypeMap.put("unsigned short", new UnsignedShortDataType());
		dataTypeMap.put("ushort", new UnsignedShortDataType());
		dataTypeMap.put("void", new VoidDataType());
		dataTypeMap.put("word", new WordDataType());
		
		// Enums, structs, and unions from debug symbols
		dataTypeMap = importEnums(dataTypeMap);
		dataTypeMap = importStructs(dataTypeMap);
		dataTypeMap = importUnions(dataTypeMap);

		// now create pointer versions of everything that's already in the hashmap
		// also create ** versions of them, since those are used too
		// and ***, in case anything really crazy is going on
		List<String> allKeys = new ArrayList<String>();
		for (String k : dataTypeMap.keySet())
		{
			allKeys.add(k);
		}
		for (String k : allKeys)
		{
			DataType directType = dataTypeMap.get(k);
			PointerDataType pointerType = new PointerDataType(directType);
			dataTypeMap.put(k + " *", pointerType);
			dataTypeMap.put(k + " (*)", pointerType);
			PointerDataType pointerPointerType = new PointerDataType(pointerType);
			dataTypeMap.put(k + " **", pointerPointerType);
			dataTypeMap.put(k + " (**)", pointerPointerType);
			PointerDataType pointerPointerPointerType = new PointerDataType(pointerPointerType);
			dataTypeMap.put(k + " ***", pointerPointerPointerType);
			dataTypeMap.put(k + " (***)", pointerPointerPointerType);
			PointerDataType pointerPointerPointerPointerType = new PointerDataType(pointerPointerPointerType);
			dataTypeMap.put(k + " ****", pointerPointerPointerPointerType);
			dataTypeMap.put(k + " (****)", pointerPointerPointerPointerType);
		}
		
		/* println("Debug: all keys in dataTypeMap: ");
		for (String k : dataTypeMap.keySet())
		{
			println(k);
		} */

		return dataTypeMap;
	}
	
	protected DataType GetDataTypeFromMap(HashMap<String, DataType> dataTypeMap, String typeName)
	{
		DataType result = dataTypeMap.get(typeName);
		if (result == null)
		{
			println("Got null result for DataType name '" + typeName + "'.");
		}
		//else
		//{
		//	println("Found non-null result for DataType name '" + typeName + "'.");
		//}
		return result;
	}

	protected Function DefineFunction(Address functionAddress, String functionName, DataType returnType, VariableStorage returnTypeVariableStorage, Parameter[] params, Function.FunctionUpdateType updateType) throws Exception
	{
		Function func = null;
		String currentTask = "searching for existing function";
		try
		{
			func = getFunctionAt(functionAddress);
			if (func == null)
			{
				currentTask = "creating the basic function";
				this.createFunction(functionAddress, functionName);
				currentTask = "getting the basic function after creation";
				func = getFunctionAt(functionAddress);
			}
			if (func == null)
			{
					println("Couldn't create function " + functionName);
					return null;
			}
			currentTask = "setting the function name";
			func.setName(functionName, SourceType.USER_DEFINED);
			int pNum = 0;
			boolean allParamsValid = true;
			for (Parameter p : params)
			{
				currentTask = "checking parameter " + Integer.toString(pNum) + " for being null";
				if (p == null)
				{
					allParamsValid = false;
					println("Not setting parameters for function '" + functionName + "' because type for parameter " + Integer.toString(pNum) + " is null.");
				}
				pNum++;
			}
			if (allParamsValid)
			{
				currentTask = "calling func.replaceParameters";
				func.replaceParameters(updateType, true, SourceType.USER_DEFINED, params);
			}
			/* set the return type last so that if custom storage is needed, it's already set by the replaceParameters call */
			if (returnType == null)
			{
				println("Not setting return type for function '" + functionName + "' because the returnType is null.");
			}
			else
			{
				currentTask = "checking to see if the return type's VariableStorage is set";
				if (returnTypeVariableStorage == null)
				{
					//println("Not setting return type variable storage for function '" + functionName + "' because returnTypeVariableStorage is null.");
					currentTask = "calling setReturnType (dynamic storage)";
					func.setReturnType(returnType, SourceType.USER_DEFINED);
				}
				else
				{
					//println("Setting return type variable storage for function '" + functionName + "'.");
					currentTask = "calling setReturn (custom storage)";
					func.setReturn(returnType, returnTypeVariableStorage, SourceType.USER_DEFINED);
				}
			}
		}
		catch (Exception e)
		{
			println("Exception thrown while creating or updating function '" + functionName + "', while " + currentTask + ": " + e.getMessage());
			println(e.toString());
			// begin: https://stackoverflow.com/questions/1149703/how-can-i-convert-a-stack-trace-to-a-string
			StringWriter sw = new StringWriter();
			PrintWriter pw = new PrintWriter(sw);
			e.printStackTrace(pw);
			String sStackTrace = sw.toString(); // stack trace as a string
			// end: https://stackoverflow.com/questions/1149703/how-can-i-convert-a-stack-trace-to-a-string
			println(sStackTrace);
			throw e;
		}
		return func;
	}
	
	protected void DefineFunction2(HashMap<String, DataType> dataTypeMap, long functionAddress, String functionName, FunctionParameter returnValue, FunctionParameter[] functionParams, boolean assignReturnAddressStorage, boolean assignAnyStorage)
	{
		boolean gotStorageForAllParams = true;
		boolean createdWithExplicitStorage = false;
		Parameter[] funcParams = new Parameter[functionParams.length];
		VariableStorage returnValueStorage = null;
		String functionAddressAndName = String.format("0x%08X", functionAddress) + " - " + functionName;
		if (assignAnyStorage)
		{
			try
			{
				if (returnValue.IsRegisterParam)
				{
					boolean createWithStorage = true;
					if ((!assignReturnAddressStorage) && (returnValue.RegisterName.equals("ra")))
					{
						createWithStorage = false;
					}
					if (!assignAnyStorage)
					{
						createWithStorage = false;
					}
					if (createWithStorage)
					{
						Register returnValueReg = findRegisterByName(returnValue.RegisterName);
						if (returnValueReg != null)
						{
							returnValueStorage = new VariableStorage(currentProgram, returnValueReg);
							println("Creating return value for function " + functionAddressAndName + " with explicit parameter storage in register " + returnValue.RegisterName);
						}
						else
						{
							createWithStorage = false;
						}
					}
					if (!createWithStorage)
					{
						/* gotStorageForAllParams = false; */
						returnValueStorage = null;
						println("Creating return value for function " + functionAddressAndName + " without explicit parameter storage");
					}
				}
				else
				{
					returnValueStorage = new VariableStorage(currentProgram, returnValue.StackOffset, returnValue.ParameterDataType.getLength());
					println("Creating return value for function " + functionAddressAndName + " with explicit parameter storage at stack offset " + String.valueOf(returnValue.StackOffset));
				}
				for (int i = 0; i < functionParams.length; i++)
				{
					String paramNumAndName = String.valueOf(i) + "(" + functionParams[i].ParameterName + ")";
					if (functionParams[i].IsRegisterParam)
					{
						Register regParam = findRegisterByName(functionParams[i].RegisterName);
						if (regParam != null)
						{
							funcParams[i] = new ParameterImpl(functionParams[i].ParameterName, functionParams[i].ParameterDataType, regParam, currentProgram);
							println("Creating parameter " + paramNumAndName + " for function " + functionAddressAndName + " with explicit parameter storage in register " + functionParams[i].RegisterName);
						}
						else
						{
							funcParams[i] = new ParameterImpl(functionParams[i].ParameterName, functionParams[i].ParameterDataType, currentProgram);
							gotStorageForAllParams = false;
							println("Creating parameter " + paramNumAndName + " for function " + functionAddressAndName + " without explicit parameter storage");
						}
					}
					else
					{
						funcParams[i] = new ParameterImpl(functionParams[i].ParameterName, functionParams[i].ParameterDataType, functionParams[i].StackOffset, currentProgram);
						println("Creating parameter " + paramNumAndName + " for function " + functionAddressAndName + " with explicit parameter storage at stack offset " + String.valueOf(functionParams[i].StackOffset));
					}
				}
				DefineFunction(toAddr(functionAddress), functionName, returnValue.ParameterDataType, returnValueStorage, funcParams, Function.FunctionUpdateType.CUSTOM_STORAGE);
				createdWithExplicitStorage = true;
				println("Created function " + functionAddressAndName + " with explicit parameter storage successfully.");
			}
			catch (Exception e)
			{
				println(e.toString() + " thrown while creating or updating function '" + functionAddressAndName + "': " + e.getMessage() + " - will attempt to create without assigning parameter storage.");
				createdWithExplicitStorage = false;
			}
			if (!gotStorageForAllParams)
			{
				println("Could not determine parameter storage for one or more parameters while creating or updating function '" + functionAddressAndName + "' - will attempt to create without assigning parameter storage.");
				createdWithExplicitStorage = false;
			}
		}
		if (!createdWithExplicitStorage)
		{
			try
			{
				for (int i = 0; i < functionParams.length; i++)
				{
					funcParams[i] = new ParameterImpl(functionParams[i].ParameterName, functionParams[i].ParameterDataType, currentProgram);
				}
				DefineFunction(toAddr(functionAddress), functionName, returnValue.ParameterDataType, null, funcParams, Function.FunctionUpdateType.DYNAMIC_STORAGE_FORMAL_PARAMS);
				println("Created function " + functionAddressAndName + " without explicit parameter storage successfully.");
			}
			catch (Exception e)
			{
				println(e.toString() + " thrown while creating or updating function '" + functionAddressAndName + "': " + e.getMessage() + ", even without explicit storage. Manual repair of the function signature in Ghidra will be required.");
			}
		}
	}
	
	protected DataType getDataTypeByName(String dataTypeName)
	{
		DataType dt = findDataTypeByName(dataTypeName);
		if (dt == null) {
			println("Could not find a datatype with the name '" + dataTypeName + "'");
			return null;
		}
		return dt;
	}
	
	protected HashMap<String, DataType> addDataTypeByName(HashMap<String, DataType> dataTypeMap, String fullGhidraName, String dataTypeName, String typeName)
	{
		DataType dt = findDataTypeByName(fullGhidraName);
		if (dt != null) {
			String putText = typeName + " " + dataTypeName;
			if (typeName.equals(""))
			{
				putText = dataTypeName;
			}
			dataTypeMap.put(putText, dt);
		}
		return dataTypeMap;
	}
	
	// from PrintStructureScript.java
	private DataType findDataTypeByName(String name) {
		PluginTool tool = state.getTool();
		DataTypeManagerService service = tool.getService(DataTypeManagerService.class);
		DataTypeManager[] dataTypeManagers = service.getDataTypeManagers();
		for (DataTypeManager manager : dataTypeManagers) {
			DataType dataType = manager.getDataType(name);
			if (dataType != null) {
				return dataType;
			}
		}
		return null;
	}

	private Register findRegisterByName(String name) {
		Register result = currentProgram.getProgramContext().getRegister(name);
		if (result == null)
		{
			println("Could not find a register with the name '" + name + "'");
		}
		return result;
	}

	protected void clearExistingData(long startAddress, long length)
	{
		for (long i = startAddress; i < startAddress + length; i++)
		{
			try
			{
				removeDataAt​(toAddr(i));
			}
			catch (Exception e)
			{
				println(e.toString() + " thrown while clearing existing data at address " + String.valueOf(i) + ": " + e.getMessage());
			}
		}
	}
	
	protected void ClearAndCreateData(long startOffset, long typeSize, DataType dt)
	{
		clearExistingData(startOffset, typeSize);
		try
		{
			createData(toAddr(startOffset), dt);
		}
		catch (Exception e)
		{
			println(e.toString() + " thrown while creating data at address " + String.valueOf(startOffset) + ": " + e.getMessage());
		}
	}
	
	protected void SetRegisterAtAddress(ProgramContext ctx, String registerName, long targetAddress, long registerValue)
	{
		try
		{
			Address ta = toAddr(targetAddress);
			BigInteger v = BigInteger.valueOf(registerValue);
			Register r = findRegisterByName(registerName);
			if (r == null)
			{
				println("Error: could not find a register named '" + registerName + "' in order to set its value.");
			}
			else
			{
				RegisterValue val = new RegisterValue(r, v);
				ctx.setRegisterValue(ta, ta, val);
			}
		}
		catch (Exception e)
		{
			println(e.toString() + " thrown while setting register '" + registerName + "' to at offset " + String.valueOf(targetAddress) + ": " + e.getMessage());
		}
	}
	

	
	/* begin: most of the code in this section is borrowed from DrMefistO's outstanding
	   PSX Loader for Ghidra (https://github.com/lab313ru/ghidra_psx_ldr) 
	   ...although I rewrote most of createSegments() to make setupMemoryMap() in a way
	   that supports all three base addresses correctly (I think).
	   Additional data from:
		https://problemkaputt.de/psx-spx.htm
		http://www.psxdev.net/forum/viewtopic.php?t=463
		http://www.raphnet.net/electronique/psx_adaptor/Playstation.txt
		http://www.psxdev.net/forum/viewtopic.php?t=488
	   - Ben Lincoln
	   
	   */
	   
	private static final long RAM_START_A = 0x00000000L;
	private static final long RAM_START_B = 0x80000000L;
	private static final long RAM_START_C = 0xA0000000L;
	private static final long RAM_ADDR_MASK = 0x00FFFFFFL;
	private static final long RAM_SIZE = 0x800000L;	// in case 0x800000 instead of 0x200000 helps support dev kit code
	   
	protected void setupMemoryMap(Program program, long programOffset, long programSize)
	{
		Memory memory = program.getMemory();
		long codeBegin0 = programOffset & RAM_ADDR_MASK;
		long codeEnd0 = codeBegin0 + programSize;
		long preCodeRAMSize = codeBegin0;
		long postCodeRAMSize = RAM_SIZE - codeEnd0;
		
		// set the text/code segment to r/w/x because that's how the PlayStation behaves
		try
		{
			MemoryBlock codeBlock = memory.getBlock(toAddr(programOffset));
			codeBlock.setRead(true);
			codeBlock.setWrite(true);
			codeBlock.setExecute(true);
		}
		catch (Exception e)
		{
			println(e.toString() + " thrown while setting text/code segment to R/W/X: " + e.getMessage());
		}
		
		FlatProgramAPI fpa = new FlatProgramAPI(program);
		
		// PlayStation games can be set up to base their addressing at 0x00000000 or 0x80000000.
		// I think it's technically possible for them to use 0xA0000000 as well. I've never seen 
		// any that do that, but I'll include support for it just in case.
		// note that for the game code, this function does NOT work like the actual PlayStation
		// hardware, which always mirrors KUSEG to KSEG0 and KSEG1. Instead, it starts with 
		// whichever segment is actually used by the game, and mirrors that to the other two.
		// The result should be what you'd see on the PlayStation, which I don't think would be 
		// the case if I mirrored empty memory to where the game code actually is.
		
		boolean foundBaseAddress = false;
		
		String kuSegNamePreCode = "RAM-KUSEG-PreCode";
		String kSeg0NamePreCode = "RAM-KSEG0-PreCode";
		String kSeg1NamePreCode = "RAM-KSEG1-PreCode";
		String kuSegNameCode = "Text-KUSEG_Mirror";
		String kSeg0NameCode = "Text-KSEG0_Mirror";
		String kSeg1NameCode = "Text-KSEG1_Mirror";
		String kuSegNamePostCode = "RAM-KUSEG-PostCode";
		String kSeg0NamePostCode = "RAM-KSEG0-PostCode";
		String kSeg1NamePostCode = "RAM-KSEG1-PostCode";
		
		// pre-code RAM
		println("Creating pre-code memory segment and mirrors");
		createSegment(fpa, null, kuSegNamePreCode, RAM_START_A, preCodeRAMSize, true, true);
		createMirrorSegment(memory, fpa, kSeg0NamePreCode, RAM_START_A, RAM_START_B, preCodeRAMSize);
		createMirrorSegment(memory, fpa, kSeg1NamePreCode, RAM_START_A, RAM_START_C, preCodeRAMSize);
		
		if (programOffset < RAM_START_B)
		{
			// start at 0x00000000, mirror everything off of that
			foundBaseAddress = true;
			println("Creating mirrors of 0x00000000 at 0x80000000 and 0xA0000000");
			// code RAM
			// code itself should already be defined, so just create the mirrors here
			createMirrorSegment(memory, fpa, kSeg0NameCode, codeBegin0, RAM_START_B + codeBegin0, programSize);
			createMirrorSegment(memory, fpa, kSeg1NameCode, codeBegin0, RAM_START_C + codeBegin0, programSize);
		}
		if ((!foundBaseAddress) && (programOffset >= RAM_START_B) && (programOffset < RAM_START_C))
		{
			// start at 0x80000000, mirror everything off of that
			foundBaseAddress = true;
			println("Creating mirrors of 0x80000000 at 0x00000000 and 0xA0000000");
			// code RAM
			// code itself should already be defined, so just create the mirrors here
			createMirrorSegment(memory, fpa, kuSegNameCode, RAM_START_B + codeBegin0, codeBegin0, programSize);
			createMirrorSegment(memory, fpa, kSeg1NameCode, RAM_START_B + codeBegin0, RAM_START_C + codeBegin0, programSize);
		}
		if (!foundBaseAddress)
		{
			// start at 0xA0000000, mirror everything off of that
			println("Creating mirrors of 0xA0000000 at 0x00000000 and 0x80000000");
			// code RAM
			// code itself should already be defined, so just create the mirrors here
			createMirrorSegment(memory, fpa, kuSegNameCode, RAM_START_C + codeBegin0, codeBegin0, programSize);
			createMirrorSegment(memory, fpa, kSeg0NameCode, RAM_START_C + codeBegin0, RAM_START_B + codeBegin0, programSize);
		}
		// post-code RAM
		println("Creating post-code memory segment and mirrors");
		createSegment(fpa, null, kuSegNamePostCode, RAM_START_A + codeEnd0, postCodeRAMSize, true, true);
		createMirrorSegment(memory, fpa, kSeg0NamePostCode, RAM_START_A + codeEnd0, RAM_START_B + codeEnd0, postCodeRAMSize);
		createMirrorSegment(memory, fpa, kSeg1NamePostCode, RAM_START_A + codeEnd0, RAM_START_C + codeEnd0, postCodeRAMSize);
		
		createSegment(fpa, null, "CACHE", 0x1F800000L, 0x400, true, true);
		createSegment(fpa, null, "UNK1", 0x1F800400L, 0xC00, true, true);
		
		println("Defining additional PlayStation-specific memory information");
		addMemCtrl1(fpa, program);
		addMemCtrl2(fpa, program);
		addPeriphIo(fpa, program);
		addIntCtrl(fpa, program);
		addDma(fpa, program);
		addTimers(fpa, program);
		addCdromRegs(fpa, program);
		addGpuRegs(fpa, program);
		addMdecRegs(fpa, program);
		addSpuVoices(fpa, program);
		addSpuCtrlRegs(fpa, program);
	}
	
	private void createSegment(FlatProgramAPI fpa, InputStream stream, String name, long address, long size, boolean write, boolean execute)
	{
		MemoryBlock block;
		try
		{
			block = fpa.createMemoryBlock(name, fpa.toAddr(address), stream, size, false);
			block.setRead(true);
			block.setWrite(write);
			block.setExecute(execute);
		}
		catch (Exception e)
		{
			println(e.toString() + " thrown while creating segment '" + name + "' at offset " + String.valueOf(address) + ": " + e.getMessage());
		}
	}
	
	private void createMirrorSegment(Memory memory, FlatProgramAPI fpa, String name, long base, long new_addr, long size)
	{
		MemoryBlock block;
		Address baseAddress = fpa.toAddr(base);
		String stepName = "calling memory.createByteMappedBlock()";
		try
		{
			block = memory.createByteMappedBlock(name, fpa.toAddr(new_addr), baseAddress, size);
			stepName = "calling memory.getBlock";
			MemoryBlock baseBlock = memory.getBlock(baseAddress);
			stepName = "setting permissions on mirror segment";
			if (baseBlock == null)
			{
				stepName = "setting default permissions on mirror segment";
				block.setRead(true);
				block.setWrite(true);
				block.setExecute(true);
			}
			else
			{
				stepName = "copying permissions from base segment";
				block.setRead(baseBlock.isRead());
				block.setWrite(baseBlock.isWrite());
				block.setExecute(baseBlock.isExecute());
			}
		}
		catch (Exception e)
		{
			println(e.toString() + " thrown while " + stepName + " during creation of mirror segment '" + name + "' at offset " + String.valueOf(new_addr) 
			+ " based on original offset " + String.valueOf(base) + ": " + e.getMessage());
		}
	}
	
	private void addMemCtrl1(FlatProgramAPI fpa, Program program)
	{
		createSegment(fpa, null, "MCTRL1", 0x1F801000L, 0x24, true, false);
		
		createNamedDword(fpa, program, 0x1F801000L, "EXP1_BASE_ADDR");
		createNamedDword(fpa, program, 0x1F801004L, "EXP2_BASE_ADDR");
		createNamedDword(fpa, program, 0x1F801008L, "EXP1_DELAY_SIZE");
		createNamedDword(fpa, program, 0x1F80100CL, "EXP3_DELAY_SIZE");
		createNamedDword(fpa, program, 0x1F801010L, "BIOS_ROM");
		createNamedDword(fpa, program, 0x1F801014L, "SPU_DELAY");
		createNamedDword(fpa, program, 0x1F801018L, "CDROM_DELAY");
		createNamedDword(fpa, program, 0x1F80101CL, "EXP2_DELAY_SIZE");
		createNamedDword(fpa, program, 0x1F801020L, "COMMON_DELAY");
	}
	
	private void addMemCtrl2(FlatProgramAPI fpa, Program program)
	{
		createSegment(fpa, null, "MCTRL2", 0x1F801060L, 4, true, false);
		
		createNamedDword(fpa, program, 0x1F801060L, "RAM_SIZE");
	}
	
	private void addPeriphIo(FlatProgramAPI fpa, Program program)
	{
		createSegment(fpa, null, "IO_PORTS", 0x1F801040L, 0x20, true, false);
		
		createNamedDword(fpa, program, 0x1F801040L, "JOY_MCD_DATA");
		createNamedDword(fpa, program, 0x1F801044L, "JOY_MCD_STAT");
		
		createNamedWord(fpa, program, 0x1F801048L, "JOY_MCD_MODE");
		createNamedWord(fpa, program, 0x1F80104AL, "JOY_MCD_CTRL");
		createNamedWord(fpa, program, 0x1F80104EL, "JOY_MCD_BAUD");
		
		createNamedDword(fpa, program, 0x1F801050L, "SIO_DATA");
		createNamedDword(fpa, program, 0x1F801054L, "SIO_STAT");
		
		createNamedWord(fpa, program, 0x1F801058L, "SIO_MODE");
		createNamedWord(fpa, program, 0x1F80105AL, "SIO_CTRL");
		createNamedWord(fpa, program, 0x1F80105CL, "SIO_MISC");
		createNamedWord(fpa, program, 0x1F80105EL, "SIO_BAUD");
	}
	
	private void addIntCtrl(FlatProgramAPI fpa, Program program)
	{
		createSegment(fpa, null, "INT_CTRL", 0x1F801070L, 6, true, false);
		
		createNamedWord(fpa, program, 0x1F801070L, "I_STAT");
		createNamedWord(fpa, program, 0x1F801074L, "I_MASK");
	}
	
	private void addDma(FlatProgramAPI fpa, Program program)
	{
		createSegment(fpa, null, "DMA_MDEC_IN", 0x1F801080L, 0x0C, true, false);
		createSegment(fpa, null, "DMA_MDEC_OUT", 0x1F801090L, 0x0C, true, false);
		createSegment(fpa, null, "DMA_GPU", 0x1F8010A0L, 0x0C, true, false);
		createSegment(fpa, null, "DMA_CDROM", 0x1F8010B0L, 0x0C, true, false);
		createSegment(fpa, null, "DMA_SPU", 0x1F8010C0L, 0x0C, true, false);
		createSegment(fpa, null, "DMA_PIO", 0x1F8010D0L, 0x0C, true, false);
		createSegment(fpa, null, "DMA_OTC", 0x1F8010E0L, 0x0C, true, false);
		createSegment(fpa, null, "DMA_CTRL_INT", 0x1F8010F0L, 0x08, true, false);
		
		createNamedDword(fpa, program, 0x1F801080L, "DMA_MDEC_IN_MADR");
		createNamedDword(fpa, program, 0x1F801084L, "DMA_MDEC_IN_BCR");
		createNamedDword(fpa, program, 0x1F801088L, "DMA_MDEC_IN_CHCR");
		
		createNamedDword(fpa, program, 0x1F801090L, "DMA_MDEC_OUT_MADR");
		createNamedDword(fpa, program, 0x1F801094L, "DMA_MDEC_OUT_BCR");
		createNamedDword(fpa, program, 0x1F801098L, "DMA_MDEC_OUT_CHCR");
		
		createNamedDword(fpa, program, 0x1F8010A0L, "DMA_GPU_MADR");
		createNamedDword(fpa, program, 0x1F8010A4L, "DMA_GPU_BCR");
		createNamedDword(fpa, program, 0x1F8010A8L, "DMA_GPU_CHCR");
		
		createNamedDword(fpa, program, 0x1F8010B0L, "DMA_CDROM_MADR");
		createNamedDword(fpa, program, 0x1F8010B4L, "DMA_CDROM_BCR");
		createNamedDword(fpa, program, 0x1F8010B8L, "DMA_CDROM_CHCR");
		
		createNamedDword(fpa, program, 0x1F8010C0L, "DMA_SPU_MADR");
		createNamedDword(fpa, program, 0x1F8010C4L, "DMA_SPU_BCR");
		createNamedDword(fpa, program, 0x1F8010C8L, "DMA_SPU_CHCR");
		
		createNamedDword(fpa, program, 0x1F8010D0L, "DMA_PIO_MADR");
		createNamedDword(fpa, program, 0x1F8010D4L, "DMA_PIO_BCR");
		createNamedDword(fpa, program, 0x1F8010D8L, "DMA_PIO_CHCR");
		
		createNamedDword(fpa, program, 0x1F8010E0L, "DMA_OTC_MADR");
		createNamedDword(fpa, program, 0x1F8010E4L, "DMA_OTC_BCR");
		createNamedDword(fpa, program, 0x1F8010E8L, "DMA_OTC_CHCR");
		
		createNamedDword(fpa, program, 0x1F8010F0L, "DMA_DPCR");
		createNamedDword(fpa, program, 0x1F8010F4L, "DMA_DICR");
	}
	
	private void addTimers(FlatProgramAPI fpa, Program program)
	{
		createSegment(fpa, null, "TMR_DOTCLOCK", 0x1F801100L, 0x10, true, false);
		createSegment(fpa, null, "TMR_HRETRACE", 0x1F801110L, 0x10, true, false);
		createSegment(fpa, null, "TMR_SYSCLOCK", 0x1F801120L, 0x10, true, false);
		
		createNamedDword(fpa, program, 0x1F801100L, "TMR_DOTCLOCK_VAL");
		createNamedDword(fpa, program, 0x1F801104L, "TMR_DOTCLOCK_MODE");
		createNamedDword(fpa, program, 0x1F801108L, "TMR_DOTCLOCK_MAX");
		
		createNamedDword(fpa, program, 0x1F801110L, "TMR_HRETRACE_VAL");
		createNamedDword(fpa, program, 0x1F801114L, "TMR_HRETRACE_MODE");
		createNamedDword(fpa, program, 0x1F801118L, "TMR_HRETRACE_MAX");
		
		createNamedDword(fpa, program, 0x1F801120L, "TMR_SYSCLOCK_VAL");
		createNamedDword(fpa, program, 0x1F801124L, "TMR_SYSCLOCK_MODE");
		createNamedDword(fpa, program, 0x1F801128L, "TMR_SYSCLOCK_MAX");
	}
	
	private void addCdromRegs(FlatProgramAPI fpa, Program program)
	{
		createSegment(fpa, null, "CDROM_REGS", 0x1F801800L, 4, true, false);
		
		createNamedByte(fpa, program, 0x1F801800L, "CDROM_REG0");
		createNamedByte(fpa, program, 0x1F801801L, "CDROM_REG1");
		createNamedByte(fpa, program, 0x1F801802L, "CDROM_REG2");
		createNamedByte(fpa, program, 0x1F801803L, "CDROM_REG3");
	}
	
	private void addGpuRegs(FlatProgramAPI fpa, Program program)
	{
		createSegment(fpa, null, "GPU_REGS", 0x1F801810L, 8, true, false);
		
		createNamedDword(fpa, program, 0x1F801810L, "GPU_REG0");
		createNamedDword(fpa, program, 0x1F801814L, "GPU_REG1");
	}
	
	private void addMdecRegs(FlatProgramAPI fpa, Program program)
	{
		createSegment(fpa, null, "MDEC_REGS", 0x1F801820L, 8, true, false);
		
		createNamedDword(fpa, program, 0x1F801820L, "MDEC_REG0");
		createNamedDword(fpa, program, 0x1F801824L, "MDEC_REG1");
	}
	
	private void addSpuVoices(FlatProgramAPI fpa, Program program)
	{
		createSegment(fpa, null, "SPU_VOICES", 0x1F801C00L, 0x10 * 24, true, false);
		
		for (int i = 0; i < 24; ++i)
		{
			createNamedDword(fpa, program, 0x1F801C00L + i * 0x10, String.format("VOICE_%02x_LEFT_RIGHT", i));
			createNamedWord(fpa, program, 0x1F801C00L + i * 0x10 + 0x04, String.format("VOICE_%02x_ADPCM_SAMPLE_RATE", i));
			createNamedWord(fpa, program, 0x1F801C00L + i * 0x10 + 0x06, String.format("VOICE_%02x_ADPCM_START_ADDR", i));
			createNamedWord(fpa, program, 0x1F801C00L + i * 0x10 + 0x08, String.format("VOICE_%02x_ADSR_ATT_DEC_SUS_REL", i));
			createNamedWord(fpa, program, 0x1F801C00L + i * 0x10 + 0x0C, String.format("VOICE_%02x_ADSR_CURR_VOLUME", i));
			createNamedWord(fpa, program, 0x1F801C00L + i * 0x10 + 0x0E, String.format("VOICE_%02x_ADPCM_REPEAT_ADDR", i));
		}
	}
	
	private void addSpuCtrlRegs(FlatProgramAPI fpa, Program program)
	{
		createSegment(fpa, null, "SPU_CTRL_REGS", 0x1F801D80L, 0x40, true, false);
		
		createNamedWord(fpa, program, 0x1F801D80L, "SPU_MAIN_VOL_L");
		createNamedWord(fpa, program, 0x1F801D82L, "SPU_MAIN_VOL_R");
		createNamedWord(fpa, program, 0x1F801D84L, "SPU_REVERB_OUT_L");
		createNamedWord(fpa, program, 0x1F801D86L, "SPU_REVERB_OUT_R");
		createNamedDword(fpa, program, 0x1F801D88L, "SPU_VOICE_KEY_ON");
		createNamedDword(fpa, program, 0x1F801D8CL, "SPU_VOICE_KEY_OFF");
		createNamedDword(fpa, program, 0x1F801D90L, "SPU_VOICE_CHN_FM_MODE");
		createNamedDword(fpa, program, 0x1F801D94L, "SPU_VOICE_CHN_NOISE_MODE");
		createNamedDword(fpa, program, 0x1F801D98L, "SPU_VOICE_CHN_REVERB_MODE");
		createNamedDword(fpa, program, 0x1F801D9CL, "SPU_VOICE_CHN_ON_OFF_STATUS");
		createNamedWord(fpa, program, 0x1F801DA0L, "SPU_UNKN_1DA0");
		createNamedWord(fpa, program, 0x1F801DA2L, "SOUND_RAM_REVERB_WORK_ADDR");
		createNamedWord(fpa, program, 0x1F801DA4L, "SOUND_RAM_IRQ_ADDR");
		createNamedWord(fpa, program, 0x1F801DA6L, "SOUND_RAM_DATA_TRANSFER_ADDR");
		createNamedWord(fpa, program, 0x1F801DA8L, "SOUND_RAM_DATA_TRANSFER_FIFO");
		createNamedWord(fpa, program, 0x1F801DAAL, "SPU_CTRL_REG_CPUCNT");
		createNamedWord(fpa, program, 0x1F801DACL, "SOUND_RAM_DATA_TRANSTER_CTRL");
		createNamedWord(fpa, program, 0x1F801DAEL, "SPU_STATUS_REG_SPUSTAT");
		createNamedWord(fpa, program, 0x1F801DB0L, "CD_VOL_L");
		createNamedWord(fpa, program, 0x1F801DB2L, "CD_VOL_R");
		createNamedWord(fpa, program, 0x1F801DB4L, "EXT_VOL_L");
		createNamedWord(fpa, program, 0x1F801DB6L, "EXT_VOL_R");
		createNamedWord(fpa, program, 0x1F801DB8L, "CURR_MAIN_VOL_L");
		createNamedWord(fpa, program, 0x1F801DBAL, "CURR_MAIN_VOL_R");
		createNamedDword(fpa, program, 0x1F801DBCL, "SPU_UNKN_1DBC");
	}
	
	private void createNamedByte(FlatProgramAPI fpa, Program program, long address, String name)
	{
		try
		{
			fpa.createByte(fpa.toAddr(address));
		}
		catch (Exception e)
		{
			println(e.toString() + " thrown while creating named byte '" + name + "' to at offset " + String.valueOf(address) + ": " + e.getMessage());
		}
		
		try
		{
			program.getSymbolTable().createLabel(fpa.toAddr(address), name, SourceType.IMPORTED);
		}
		catch (InvalidInputException e)
		{
			println(e.toString() + " thrown while label for named byte '" + name + "' to at offset " + String.valueOf(address) + ": " + e.getMessage());
		}
	}
	
	private void createNamedWord(FlatProgramAPI fpa, Program program, long address, String name)
	{
		try
		{
			fpa.createWord(fpa.toAddr(address));
		}
		catch (Exception e)
		{
			println(e.toString() + " thrown while creating named word '" + name + "' to at offset " + String.valueOf(address) + ": " + e.getMessage());
		}
		
		try
		{
			program.getSymbolTable().createLabel(fpa.toAddr(address), name, SourceType.IMPORTED);
		}
		catch (InvalidInputException e)
		{
			println(e.toString() + " thrown while creating label for named word '" + name + "' to at offset " + String.valueOf(address) + ": " + e.getMessage());
		}
	}
	
	private void createNamedDword(FlatProgramAPI fpa, Program program, long address, String name)
	{
		try
		{
			fpa.createDWord(fpa.toAddr(address));
		}
		catch (Exception e)
		{
			println(e.toString() + " thrown while creating named dword '" + name + "' to at offset " + String.valueOf(address) + ": " + e.getMessage());
		}
		
		try
		{
			program.getSymbolTable().createLabel(fpa.toAddr(address), name, SourceType.IMPORTED);
		}
		catch (InvalidInputException e)
		{
			println(e.toString() + " thrown while creating label for named dword '" + name + "' to at offset " + String.valueOf(address) + ": " + e.getMessage());
		}
	}

	/* end: most of the code in this section is borrowed from DrMefistO's outstanding
	   PSX Loader for Ghidra (https://github.com/lab313ru/ghidra_psx_ldr) */
	
protected HashMap<String, DataType> importEnums(HashMap<String, DataType> dataTypeMap)
	{
%ENUM_IMPORT_SUBROUTINE_CALLS%
		return dataTypeMap;
	}

protected HashMap<String, DataType> importStructs(HashMap<String, DataType> dataTypeMap)
	{
%STRUCT_IMPORT_SUBROUTINE_CALLS%
		return dataTypeMap;
	}

protected HashMap<String, DataType> importUnions(HashMap<String, DataType> dataTypeMap)
	{
%UNION_IMPORT_SUBROUTINE_CALLS%
		return dataTypeMap;
	}

protected HashMap<String, DataType> importFunctionPointers(HashMap<String, DataType> dataTypeMap)
	{
%FUNCTION_POINTER_IMPORTS%
		return dataTypeMap;
	}

// DataType import subroutines (begin)

// enum import subroutines (begin)
%ENUM_IMPORT_SUBROUTINES%
// enum import subroutines (end)

// struct import subroutines (begin)
%STRUCT_IMPORT_SUBROUTINES%
// struct import subroutines (end)

// union import subroutines (begin)
%UNION_IMPORT_SUBROUTINES%
// union import subroutines (end)

// DataType import subroutines (end)


// Label creation subroutines (begin)
%LABEL_CREATION_SUBROUTINES%
// Label creation subroutines (end)

}