// Copyright (c) 2012- PPSSPP Project.

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.

// This program 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 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.

#include "ppsspp_config.h"

#include <string>
#include <algorithm>
#include <map>

#include "ext/xxhash.h"

#include "Common/CommonTypes.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/Log.h"
#include "Common/StringUtils.h"
#include "Core/MemMap.h"
#include "Core/System.h"
#include "Core/MIPS/MIPSDebugInterface.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/Debugger/DebugInterface.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/Debugger/DisassemblyManager.h"

DisassemblyManager g_disassemblyManager;

bool isInInterval(u32 start, u32 size, u32 value) {
	return start <= value && value <= (start+size-1);
}

bool IsLikelyStringAt(uint32_t addr) {
	uint32_t maxLen = Memory::ValidSize(addr, 128);
	if (maxLen <= 1)
		return false;
	const char *p = Memory::GetCharPointer(addr);
	// If there's no terminator nearby, let's say no.
	if (memchr(p, 0, maxLen) == nullptr)
		return false;

	// Allow tabs and newlines.
	static constexpr bool validControl[] = {
		false, false, false, false, false, false, false, false,
		false, true, true, true, false, true, false, false,
		false, false, false, false, false, false, false, false,
		false, false, false, false, false, false, false, false,
	};

	// Check that there's some bytes before the terminator that look like a string.
	UTF8 utf(p);
	if (utf.end())
		return false;

	char verify[4];
	while (!utf.end()) {
		if (utf.invalid())
			return false;

		int pos = utf.byteIndex();
		uint32_t c = utf.next();
		int len = UTF8::encode(verify, c);
		// Our decoder is a bit lax, so let's verify this is a normal encoding.
		// This prevents us from trying to output invalid encodings in the debugger.
		if (memcmp(p + pos, verify, len) != 0 || pos + len != utf.byteIndex())
			return false;

		if (c < ARRAY_SIZE(validControl) && !validControl[c])
			return false;
		if (c > 0x0010FFFF)
			return false;
	}

	return true;
}

static HashType computeHash(u32 address, u32 size)
{
	if (!Memory::IsValidAddress(address))
		return 0;

	size = Memory::ValidSize(address, size);
#if PPSSPP_ARCH(AMD64)
	return XXH3_64bits(Memory::GetPointerUnchecked(address), size);
#else
	return XXH3_64bits(Memory::GetPointerUnchecked(address), size) & 0xFFFFFFFF;
#endif
}


static void parseDisasm(const char *disasm, char *opcode, size_t opcodeSize, char *arguments, size_t argumentsSize, bool insertSymbols) {
	// copy opcode
	size_t opcodePos = 0;
	while (*disasm != 0 && *disasm != '\t' && opcodePos + 1 < opcodeSize) {
		opcode[opcodePos++] = *disasm++;
	}
	opcode[opcodePos] = 0;

	// Otherwise it's a tab, and we skip intentionally.
	if (*disasm++ == 0) {
		*arguments = 0;
		return;
	}

	const char* jumpAddress = strstr(disasm,"->$");
	const char* jumpRegister = strstr(disasm,"->");
	size_t argumentsPos = 0;
	while (*disasm != 0 && argumentsPos + 1 < argumentsSize) {
		// parse symbol
		if (disasm == jumpAddress) {
			u32 branchTarget = 0;
			sscanf(disasm+3, "%08x", &branchTarget);
			const std::string addressSymbol = g_symbolMap->GetLabelString(branchTarget);
			if (!addressSymbol.empty() && insertSymbols) {
				argumentsPos += snprintf(&arguments[argumentsPos], argumentsSize - argumentsPos, "%s", addressSymbol.c_str());
			} else {
				argumentsPos += snprintf(&arguments[argumentsPos], argumentsSize - argumentsPos, "0x%08X", branchTarget);
			}

			disasm += 3+8;
			continue;
		}

		if (disasm == jumpRegister)
			disasm += 2;

		if (*disasm == ' ') {
			disasm++;
			continue;
		}
		arguments[argumentsPos++] = *disasm++;
	}

	arguments[argumentsPos] = 0;
}

std::map<u32,DisassemblyEntry*>::iterator findDisassemblyEntry(std::map<u32,DisassemblyEntry*>& entries, u32 address, bool exact)
{
	if (exact)
		return entries.find(address);

	if (entries.size() == 0)
		return entries.end();

	// find first elem that's >= address
	auto it = entries.lower_bound(address);
	if (it != entries.end())
	{
		// it may be an exact match
		if (isInInterval(it->second->getLineAddress(0),it->second->getTotalSize(),address))
			return it;

		// otherwise it may point to the next
		if (it != entries.begin())
		{
			it--;
			if (isInInterval(it->second->getLineAddress(0),it->second->getTotalSize(),address))
				return it;
		}
	}

	// check last entry manually
	auto rit = entries.rbegin();
	if (isInInterval(rit->second->getLineAddress(0),rit->second->getTotalSize(),address))
	{
		return (++rit).base();
	}

	// no match otherwise
	return entries.end();
}

void DisassemblyManager::setCpu(DebugInterface *cpu) {
	_dbg_assert_(cpu);
	cpu_ = cpu;
};

void DisassemblyManager::analyze(u32 address, u32 size = 1024)
{
	u32 end = address+size;

	address &= ~3;
	u32 start = address;

	while (address < end && start <= address)
	{
		if (!PSP_IsInited())
			return;

		std::lock_guard<std::recursive_mutex> guard(entriesLock_);
		auto it = findDisassemblyEntry(entries, address, false);
		if (it != entries.end())
		{
			DisassemblyEntry* entry = it->second;
			entry->recheck();
			address = entry->getLineAddress(0)+entry->getTotalSize();
			continue;
		}

		SymbolInfo info;
		if (!g_symbolMap->GetSymbolInfo(&info,address,ST_ALL))
		{
			if (address % 4)
			{
				u32 next = std::min<u32>((address+3) & ~3,g_symbolMap->GetNextSymbolAddress(address,ST_ALL));
				DisassemblyData* data = new DisassemblyData(address,next-address,DATATYPE_BYTE);
				entries[address] = data;
				address = next;
				continue;
			}

			u32 next = g_symbolMap->GetNextSymbolAddress(address,ST_ALL);

			if ((next % 4) && next != (u32)-1)
			{
				u32 alignedNext = next & ~3;

				if (alignedNext != address)
				{
					DisassemblyOpcode* opcode = new DisassemblyOpcode(address,(alignedNext-address)/4);
					entries[address] = opcode;
				}

				DisassemblyData* data = new DisassemblyData(address,next-alignedNext,DATATYPE_BYTE);
				entries[alignedNext] = data;
			} else {
				DisassemblyOpcode* opcode = new DisassemblyOpcode(address,(next-address)/4);
				entries[address] = opcode;
			}

			address = next;
			continue;
		}

		switch (info.type)
		{
		case ST_FUNCTION:
			{
				DisassemblyFunction* function = new DisassemblyFunction(info.address,info.size);
				entries[info.address] = function;
				address = info.address+info.size;
			}
			break;
		case ST_DATA:
			{
				DisassemblyData* data = new DisassemblyData(info.address,info.size,g_symbolMap->GetDataType(info.address));
				entries[info.address] = data;
				address = info.address+info.size;
			}
			break;
		default:
			break;
		}
	}

}

std::vector<BranchLine> DisassemblyManager::getBranchLines(u32 start, u32 size)
{
	std::vector<BranchLine> result;

	std::lock_guard<std::recursive_mutex> guard(entriesLock_);
	auto it = findDisassemblyEntry(entries,start,false);
	if (it != entries.end())
	{
		do 
		{
			it->second->getBranchLines(start,size,result);
			it++;
		} while (it != entries.end() && start+size > it->second->getLineAddress(0));
	}

	return result;
}

void DisassemblyManager::getLine(u32 address, bool insertSymbols, DisassemblyLineInfo &dest, DebugInterface *cpuDebug)
{
	std::lock_guard<std::recursive_mutex> guard(entriesLock_);
	auto it = findDisassemblyEntry(entries,address,false);
	if (it == entries.end()) {
		analyze(address);
		it = findDisassemblyEntry(entries,address,false);
	}

	if (it != entries.end()) {
		DisassemblyEntry *entry = it->second;
		if (entry->disassemble(address, dest, insertSymbols, cpuDebug))
			return;
	}

	dest.type = DISTYPE_OTHER;
	memset(&dest.info, 0, sizeof(dest.info));
	dest.info.opcodeAddress = address;
	if (address % 4)
		dest.totalSize = ((address+3) & ~3)-address;
	else
		dest.totalSize = 4;
	if (Memory::IsValidRange(address, 4)) {
		dest.name = "ERROR";
		dest.params = "Disassembly failure";
	} else {
		dest.name = "-";
		dest.params.clear();
	}
}

u32 DisassemblyManager::getStartAddress(u32 address)
{
	std::lock_guard<std::recursive_mutex> guard(entriesLock_);
	auto it = findDisassemblyEntry(entries,address,false);
	if (it == entries.end())
	{
		analyze(address);
		it = findDisassemblyEntry(entries,address,false);
		if (it == entries.end())
			return address;
	}
	
	DisassemblyEntry* entry = it->second;
	int line = entry->getLineNum(address,true);
	return entry->getLineAddress(line);
}

u32 DisassemblyManager::getNthPreviousAddress(u32 address, int n)
{
	std::lock_guard<std::recursive_mutex> guard(entriesLock_);
	while (Memory::IsValidAddress(address))
	{
		auto it = findDisassemblyEntry(entries,address,false);
		if (it == entries.end())
			break;
		while (it != entries.end()) {
			DisassemblyEntry* entry = it->second;
			int oldLineNum = entry->getLineNum(address,true);
			if (n <= oldLineNum)
			{
				return entry->getLineAddress(oldLineNum-n);
			}

			address = entry->getLineAddress(0)-1;
			n -= oldLineNum+1;
			it = findDisassemblyEntry(entries,address,false);
		}
	
		analyze(address-127,128);
	}
	
	return (address - n * 4) & ~3;
}

u32 DisassemblyManager::getNthNextAddress(u32 address, int n)
{
	std::lock_guard<std::recursive_mutex> guard(entriesLock_);
	while (Memory::IsValidAddress(address))
	{
		auto it = findDisassemblyEntry(entries,address,false);
		if (it == entries.end()) {
			break;
		}

		while (it != entries.end())
		{
			DisassemblyEntry* entry = it->second;
			int oldLineNum = entry->getLineNum(address,true);
			int oldNumLines = entry->getNumLines();
			if (oldLineNum+n < oldNumLines)
			{
				return entry->getLineAddress(oldLineNum+n);
			}

			address = entry->getLineAddress(0)+entry->getTotalSize();
			n -= (oldNumLines-oldLineNum);
			it = findDisassemblyEntry(entries,address,false);
		}

		analyze(address);
	}

	return (address + n * 4) & ~3;
}

DisassemblyManager::~DisassemblyManager() {
}

void DisassemblyManager::clear()
{
	std::lock_guard<std::recursive_mutex> guard(entriesLock_);
	for (auto it = entries.begin(); it != entries.end(); it++)
	{
		delete it->second;
	}
	entries.clear();
}

DisassemblyFunction::DisassemblyFunction(u32 _address, u32 _size): address(_address), size(_size)
{
	if (!PSP_IsInited())
		return;

	hash = computeHash(address,size);
	load();
}

DisassemblyFunction::~DisassemblyFunction() {
	clear();
}

void DisassemblyFunction::recheck()
{
	if (!PSP_IsInited())
		return;

	HashType newHash = computeHash(address,size);
	if (hash != newHash)
	{
		hash = newHash;
		clear();
		load();
	}
}

int DisassemblyFunction::getNumLines()
{
	std::lock_guard<std::recursive_mutex> guard(lock_);
	return (int) lineAddresses.size();
}

int DisassemblyFunction::getLineNum(u32 address, bool findStart)
{
	std::lock_guard<std::recursive_mutex> guard(lock_);
	if (findStart)
	{
		int last = (int)lineAddresses.size() - 1;
		for (int i = 0; i < last; i++)
		{
			u32 next = lineAddresses[i + 1];
			if (lineAddresses[i] <= address && next > address)
				return i;
		}
		if (lineAddresses[last] <= address && this->address + this->size > address)
			return last;
	}
	else
	{
		int last = (int)lineAddresses.size() - 1;
		for (int i = 0; i < last; i++)
		{
			if (lineAddresses[i] == address)
				return i;
		}
		if (lineAddresses[last] == address)
			return last;
	}

	return 0;
}

u32 DisassemblyFunction::getLineAddress(int line)
{
	std::lock_guard<std::recursive_mutex> guard(lock_);
	return lineAddresses[line];
}

bool DisassemblyFunction::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug)
{
	std::lock_guard<std::recursive_mutex> guard(lock_);
	auto it = findDisassemblyEntry(entries,address,false);
	if (it == entries.end())
		return false;

	return it->second->disassemble(address, dest, insertSymbols, cpuDebug);
}

void DisassemblyFunction::getBranchLines(u32 start, u32 size, std::vector<BranchLine>& dest)
{
	u32 end = start+size;

	std::lock_guard<std::recursive_mutex> guard(lock_);
	for (size_t i = 0; i < lines.size(); i++)
	{
		BranchLine& line = lines[i];

		u32 first = line.first;
		u32 second = line.second;

		// skip branches that are entirely before or entirely after the window
		if ((first < start && second < start) ||
			(first > end && second > end))
			continue;

		dest.push_back(line);
	}
}

#define NUM_LANES 16

void DisassemblyFunction::generateBranchLines()
{
	struct LaneInfo
	{
		bool used;
		u32 end;
	};

	LaneInfo lanes[NUM_LANES];
	for (int i = 0; i < NUM_LANES; i++)
		lanes[i].used = false;

	u32 end = address+size;

	std::lock_guard<std::recursive_mutex> guard(lock_);
	DebugInterface *cpu = g_disassemblyManager.getCpu();
	for (u32 funcPos = address; funcPos < end; funcPos += 4)
	{
		MIPSAnalyst::MipsOpcodeInfo opInfo = MIPSAnalyst::GetOpcodeInfo(cpu, funcPos);

		bool inFunction = (opInfo.branchTarget >= address && opInfo.branchTarget < end);
		if (opInfo.isBranch && !opInfo.isBranchToRegister && !opInfo.isLinkedBranch && inFunction) {
			if (!Memory::IsValidAddress(opInfo.branchTarget))
				continue;

			BranchLine line;
			if (opInfo.branchTarget < funcPos) {
				line.first = opInfo.branchTarget;
				line.second = funcPos;
				line.type = LINE_UP;
			} else {
				line.first = funcPos;
				line.second = opInfo.branchTarget;
				line.type = LINE_DOWN;
			}

			lines.push_back(line);
		}
	}
			
	std::sort(lines.begin(),lines.end());
	for (size_t i = 0; i < lines.size(); i++)
	{
		for (int l = 0; l < NUM_LANES; l++)
		{
			if (lines[i].first > lanes[l].end)
				lanes[l].used = false;
		}

		int lane = -1;
		for (int l = 0; l < NUM_LANES; l++)
		{
			if (lanes[l].used == false)
			{
				lane = l;
				break;
			}
		}

		if (lane == -1)
		{
			// Let's just pile on.
			lines[i].laneIndex = 15;
			continue;
		}

		lanes[lane].end = lines[i].second;
		lanes[lane].used = true;
		lines[i].laneIndex = lane;
	}
}

void DisassemblyFunction::addOpcodeSequence(u32 start, u32 end)
{
	DisassemblyOpcode* opcode = new DisassemblyOpcode(start,(end-start)/4);
	std::lock_guard<std::recursive_mutex> guard(lock_);
	entries[start] = opcode;
	lineAddresses.reserve((end - start) / 4);
	for (u32 pos = start; pos < end; pos += 4)
	{
		lineAddresses.push_back(pos);
	}
}

void DisassemblyFunction::load()
{
	generateBranchLines();

	// gather all branch targets
	std::set<u32> branchTargets;
	{
		std::lock_guard<std::recursive_mutex> guard(lock_);
		for (size_t i = 0; i < lines.size(); i++)
		{
			switch (lines[i].type)
			{
			case LINE_DOWN:
				branchTargets.insert(lines[i].second);
				break;
			case LINE_UP:
				branchTargets.insert(lines[i].first);
				break;
			default:
				break;
			}
		}
	}
	
	DebugInterface *cpu = g_disassemblyManager.getCpu();
	u32 funcPos = address;
	u32 funcEnd = address+size;

	u32 nextData = g_symbolMap->GetNextSymbolAddress(funcPos-1,ST_DATA);
	u32 opcodeSequenceStart = funcPos;
	while (funcPos < funcEnd)
	{
		if (funcPos == nextData)
		{
			if (opcodeSequenceStart != funcPos)
				addOpcodeSequence(opcodeSequenceStart,funcPos);

			DisassemblyData* data = new DisassemblyData(funcPos,g_symbolMap->GetDataSize(funcPos),g_symbolMap->GetDataType(funcPos));
			std::lock_guard<std::recursive_mutex> guard(lock_);
			entries[funcPos] = data;
			lineAddresses.push_back(funcPos);
			funcPos += data->getTotalSize();

			nextData = g_symbolMap->GetNextSymbolAddress(funcPos-1,ST_DATA);
			opcodeSequenceStart = funcPos;
			continue;
		}

		// force align
		if (funcPos % 4)
		{
			u32 nextPos = (funcPos+3) & ~3;

			DisassemblyComment* comment = new DisassemblyComment(funcPos,nextPos-funcPos,".align","4");
			std::lock_guard<std::recursive_mutex> guard(lock_);
			entries[funcPos] = comment;
			lineAddresses.push_back(funcPos);
			
			funcPos = nextPos;
			opcodeSequenceStart = funcPos;
			continue;
		}

		MIPSAnalyst::MipsOpcodeInfo opInfo = MIPSAnalyst::GetOpcodeInfo(cpu,funcPos);
		u32 opAddress = funcPos;
		funcPos += 4;

		// skip branches and their delay slots
		if (opInfo.isBranch)
		{
			funcPos += 4;
			continue;
		}

		// lui
		if (MIPS_GET_OP(opInfo.encodedOpcode) == 0x0F && funcPos < funcEnd && funcPos != nextData)
		{
			MIPSOpcode next = Memory::Read_Instruction(funcPos);
			MIPSInfo nextInfo = MIPSGetInfo(next);

			u32 immediate = ((opInfo.encodedOpcode & 0xFFFF) << 16) + (s16)(next.encoding & 0xFFFF);
			int rt = MIPS_GET_RT(opInfo.encodedOpcode);

			int nextRs = MIPS_GET_RS(next.encoding);
			int nextRt = MIPS_GET_RT(next.encoding);

			// both rs and rt of the second op have to match rt of the first,
			// otherwise there may be hidden consequences if the macro is displayed.
			// also, don't create a macro if something branches into the middle of it
			if (nextRs == rt && nextRt == rt && branchTargets.find(funcPos) == branchTargets.end())
			{
				DisassemblyMacro* macro = NULL;
				switch (MIPS_GET_OP(next.encoding))
				{
				case 0x09:	// addiu
					macro = new DisassemblyMacro(opAddress);
					macro->setMacroLi(immediate,rt);
					funcPos += 4;
					break;
				case 0x20:	// lb
				case 0x21:	// lh
				case 0x23:	// lw
				case 0x24:	// lbu
				case 0x25:	// lhu
				case 0x28:	// sb
				case 0x29:	// sh
				case 0x2B:	// sw
					macro = new DisassemblyMacro(opAddress);
					
					int dataSize = MIPSGetMemoryAccessSize(next);
					if (dataSize == 0) {
						delete macro;
						return;
					}

					macro->setMacroMemory(MIPSGetName(next),immediate,rt,dataSize);
					funcPos += 4;
					break;
				}

				if (macro != NULL)
				{
					if (opcodeSequenceStart != opAddress)
						addOpcodeSequence(opcodeSequenceStart,opAddress);

					std::lock_guard<std::recursive_mutex> guard(lock_);
					entries[opAddress] = macro;
					for (int i = 0; i < macro->getNumLines(); i++)
					{
						lineAddresses.push_back(macro->getLineAddress(i));
					}

					opcodeSequenceStart = funcPos;
					continue;
				}
			}
		}

		// just a normal opcode
	}

	if (opcodeSequenceStart != funcPos)
		addOpcodeSequence(opcodeSequenceStart,funcPos);
}

void DisassemblyFunction::clear()
{
	std::lock_guard<std::recursive_mutex> guard(lock_);
	for (auto it = entries.begin(); it != entries.end(); it++)
	{
		delete it->second;
	}

	entries.clear();
	lines.clear();
	lineAddresses.clear();
	hash = 0;
}

bool DisassemblyOpcode::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) {
	char opcode[64],arguments[256];
	char dizz[512];
	DisAsm(address, dizz, sizeof(dizz));
	parseDisasm(dizz, opcode, sizeof(opcode), arguments, sizeof(arguments), insertSymbols);
	dest.type = DISTYPE_OPCODE;
	dest.name = opcode;
	dest.params = arguments;
	dest.totalSize = 4;
	dest.info = MIPSAnalyst::GetOpcodeInfo(cpuDebug, address);
	return true;
}

void DisassemblyOpcode::getBranchLines(u32 start, u32 size, std::vector<BranchLine>& dest)
{
	if (start < address)
	{
		size = start+size-address;
		start = address;
	}

	if (start+size > address+num*4)
		size = address+num*4-start;

	int lane = 0;
	for (u32 pos = start; pos < start+size; pos += 4)
	{
		MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(g_disassemblyManager.getCpu(),pos);
		if (info.isBranch && !info.isBranchToRegister && !info.isLinkedBranch) {
			if (!Memory::IsValidAddress(info.branchTarget))
				continue;

			BranchLine line;
			line.laneIndex = lane++;

			if (info.branchTarget < pos) {
				line.first = info.branchTarget;
				line.second = pos;
				line.type = LINE_UP;
			} else {
				line.first = pos;
				line.second = info.branchTarget;
				line.type = LINE_DOWN;
			}

			dest.push_back(line);
		}
	}
}


void DisassemblyMacro::setMacroLi(u32 _immediate, u8 _rt)
{
	type = MACRO_LI;
	name = "li";
	immediate = _immediate;
	rt = _rt;
	numOpcodes = 2;
}

void DisassemblyMacro::setMacroMemory(std::string_view _name, u32 _immediate, u8 _rt, int _dataSize)
{
	type = MACRO_MEMORYIMM;
	name = _name;
	immediate = _immediate;
	rt = _rt;
	dataSize = _dataSize;
	numOpcodes = 2;
}

bool DisassemblyMacro::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug)
{
	char buffer[64];
	dest.type = DISTYPE_MACRO;
	dest.info = MIPSAnalyst::GetOpcodeInfo(cpuDebug, address);

	std::string addressSymbol;
	switch (type)
	{
	case MACRO_LI:
		dest.name = name;
		
		addressSymbol = g_symbolMap->GetLabelString(immediate);
		if (!addressSymbol.empty() && insertSymbols) {
			snprintf(buffer, sizeof(buffer), "%s,%s", MIPSDebugInterface::GetRegName(0, rt).c_str(), addressSymbol.c_str());
		} else {
			snprintf(buffer, sizeof(buffer), "%s,0x%08X", MIPSDebugInterface::GetRegName(0, rt).c_str(), immediate);
		}

		dest.params = buffer;
		
		dest.info.hasRelevantAddress = true;
		dest.info.relevantAddress = immediate;
		break;
	case MACRO_MEMORYIMM:
		dest.name = name;

		addressSymbol = g_symbolMap->GetLabelString(immediate);
		if (!addressSymbol.empty() && insertSymbols) {
			snprintf(buffer, sizeof(buffer), "%s,%s", MIPSDebugInterface::GetRegName(0, rt).c_str(), addressSymbol.c_str());
		} else {
			snprintf(buffer, sizeof(buffer), "%s,0x%08X", MIPSDebugInterface::GetRegName(0, rt).c_str(), immediate);
		}

		dest.params = buffer;

		dest.info.isDataAccess = true;
		dest.info.dataAddress = immediate;
		dest.info.dataSize = dataSize;

		dest.info.hasRelevantAddress = true;
		dest.info.relevantAddress = immediate;
		break;
	default:
		return false;
	}

	dest.totalSize = getTotalSize();
	return true;
}

DisassemblyData::DisassemblyData(u32 _address, u32 _size, DataType _type): address(_address), size(_size), type(_type)
{
	_dbg_assert_(PSP_IsInited());
	hash = computeHash(address,size);
	createLines();
}

void DisassemblyData::recheck()
{
	if (!PSP_IsInited())
		return;

	HashType newHash = computeHash(address,size);
	if (newHash != hash)
	{
		hash = newHash;
		createLines();
	}
}

bool DisassemblyData::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug)
{
	dest.type = DISTYPE_DATA;

	switch (type)
	{
	case DATATYPE_BYTE:
		dest.name = ".byte";
		break;
	case DATATYPE_HALFWORD:
		dest.name = ".half";
		break;
	case DATATYPE_WORD:
		dest.name = ".word";
		break;
	case DATATYPE_ASCII:
		dest.name = ".ascii";
		break;
	default:
		return false;
	}

	std::lock_guard<std::recursive_mutex> guard(lock_);
	auto it = lines.find(address);
	if (it == lines.end())
		return false;

	dest.params = it->second.text;
	dest.totalSize = it->second.size;
	return true;
}

int DisassemblyData::getLineNum(u32 address, bool findStart)
{
	std::lock_guard<std::recursive_mutex> guard(lock_);
	auto it = lines.upper_bound(address);
	if (it != lines.end())
	{
		if (it == lines.begin())
			return 0;
		it--;
		return it->second.lineNum;
	}

	return lines.rbegin()->second.lineNum;
}

void DisassemblyData::createLines()
{
	std::lock_guard<std::recursive_mutex> guard(lock_);
	lines.clear();
	lineAddresses.clear();

	u32 pos = address;
	const u32 end = address+size;
	const u32 maxChars = g_disassemblyManager.getMaxParamChars();
	
	std::string currentLine;
	u32 currentLineStart = pos;

	int lineCount = 0;
	if (type == DATATYPE_ASCII)
	{
		bool inString = false;
		while (pos < end)
		{
			u8 b = Memory::Read_U8(pos++);
			if (b >= 0x20 && b <= 0x7F)
			{
				if (currentLine.size()+1 >= maxChars)
				{
					if (inString == true)
						currentLine += "\"";

					DataEntry entry = {currentLine,pos-1-currentLineStart,lineCount++};
					lines[currentLineStart] = entry;
					lineAddresses.push_back(currentLineStart);
					
					currentLine.clear();
					currentLineStart = pos-1;
					inString = false;
				}

				if (inString == false)
					currentLine += "\"";
				currentLine += (char)b;
				inString = true;
			} else {
				char buffer[64];
				if (pos == end && b == 0)
					truncate_cpy(buffer, "0");
				else
					snprintf(buffer, sizeof(buffer), "0x%02X", b);

				if (currentLine.size()+strlen(buffer) >= maxChars)
				{
					if (inString == true)
						currentLine += "\"";
					
					DataEntry entry = {currentLine,pos-1-currentLineStart,lineCount++};
					lines[currentLineStart] = entry;
					lineAddresses.push_back(currentLineStart);
					
					currentLine.clear();
					currentLineStart = pos-1;
					inString = false;
				}

				bool comma = false;
				if (currentLine.size() != 0)
					comma = true;

				if (inString)
					currentLine += "\"";

				if (comma)
					currentLine += ",";

				currentLine += buffer;
				inString = false;
			}
		}

		if (inString == true)
			currentLine += "\"";

		if (currentLine.size() != 0)
		{
			DataEntry entry = {currentLine,pos-currentLineStart,lineCount++};
			lines[currentLineStart] = entry;
			lineAddresses.push_back(currentLineStart);
		}
	} else {
		while (pos < end)
		{
			char buffer[256];
			u32 value;

			u32 currentPos = pos;

			switch (type)
			{
			case DATATYPE_BYTE:
				value = Memory::Read_U8(pos);
				snprintf(buffer, sizeof(buffer), "0x%02X", value);
				pos++;
				break;
			case DATATYPE_HALFWORD:
				value = Memory::Read_U16(pos);
				snprintf(buffer, sizeof(buffer), "0x%04X", value);
				pos += 2;
				break;
			case DATATYPE_WORD:
				{
					value = Memory::Read_U32(pos);
					const std::string label = g_symbolMap->GetLabelString(value);
					if (!label.empty())
						snprintf(buffer, sizeof(buffer), "%s", label.c_str());
					else
						snprintf(buffer, sizeof(buffer), "0x%08X", value);
					pos += 4;
				}
				break;
			default:
				break;
			}

			size_t len = strlen(buffer);
			if (currentLine.size() != 0 && currentLine.size()+len >= maxChars)
			{
				DataEntry entry = {currentLine,currentPos-currentLineStart,lineCount++};
				lines[currentLineStart] = entry;
				lineAddresses.push_back(currentLineStart);

				currentLine.clear();
				currentLineStart = currentPos;
			}

			if (currentLine.size() != 0)
				currentLine += ",";
			currentLine += buffer;
		}

		if (currentLine.size() != 0) {
			DataEntry entry = {currentLine,pos-currentLineStart,lineCount++};
			lines[currentLineStart] = entry;
			lineAddresses.push_back(currentLineStart);
		}
	}
}


DisassemblyComment::DisassemblyComment(u32 _address, u32 _size, std::string_view _name, std::string_view _param)
	: address(_address), size(_size), name(_name), param(_param) {}

bool DisassemblyComment::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) {
	dest.type = DISTYPE_OTHER;
	dest.name = name;
	dest.params = param;
	dest.totalSize = size;
	return true;
}

bool GetDisasmAddressText(u32 address, char* dest, bool abbreviateLabels, bool showData, bool displaySymbols) {
	if (displaySymbols) {
		const std::string addressSymbol = g_symbolMap->GetLabelString(address);
		if (!addressSymbol.empty()) {
			for (int k = 0; addressSymbol[k] != 0; k++) {
				// abbreviate long names
				if (abbreviateLabels && k == 16 && addressSymbol[k+1] != 0) {
					*dest++ = '+';
					break;
				}
				*dest++ = addressSymbol[k];
			}
			*dest++ = ':';
			*dest = 0;
			return true;
		} else {
			sprintf(dest,"    %08X",address);
			return false;
		}
	} else {
		if (showData) {
			u32 encoding = Memory::IsValidAddress(address) ? Memory::Read_Instruction(address, true).encoding : 0;
			sprintf(dest, "%08X %08X", address, encoding);
		} else {
			sprintf(dest, "%08X", address);
		}
		return false;
	}
}

// Utilify function from the old debugger.
std::string DisassembleRange(u32 start, u32 size, bool displaySymbols, MIPSDebugInterface *debugger) {
	auto memLock = Memory::Lock();
	std::string result;

	// gather all branch targets without labels
	std::set<u32> branchAddresses;
	for (u32 i = 0; i < size; i += debugger->getInstructionSize(0)) {
		MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(debugger, start + i);

		if (info.isBranch && g_symbolMap->GetLabelString(info.branchTarget).empty()) {
			if (branchAddresses.find(info.branchTarget) == branchAddresses.end()) {
				branchAddresses.insert(info.branchTarget);
			}
		}
	}

	u32 disAddress = start;
	bool previousLabel = true;
	DisassemblyLineInfo line;
	while (disAddress < start + size) {
		char addressText[64], buffer[512];

		g_disassemblyManager.getLine(disAddress, displaySymbols, line, debugger);
		bool isLabel = GetDisasmAddressText(disAddress, addressText, false, line.type == DISTYPE_OPCODE, displaySymbols);

		if (isLabel) {
			if (!previousLabel)
				result += "\r\n";
			sprintf(buffer, "%s\r\n\r\n", addressText);
			result += buffer;
		} else if (branchAddresses.find(disAddress) != branchAddresses.end()) {
			if (!previousLabel)
				result += "\r\n";
			sprintf(buffer, "pos_%08X:\r\n\r\n", disAddress);
			result += buffer;
		}

		if (line.info.isBranch && !line.info.isBranchToRegister
			&& g_symbolMap->GetLabelString(line.info.branchTarget).empty()
			&& branchAddresses.find(line.info.branchTarget) != branchAddresses.end()) {
			sprintf(buffer, "pos_%08X", line.info.branchTarget);
			line.params = line.params.substr(0, line.params.find("0x")) + buffer;
		}

		sprintf(buffer, "\t%s\t%s\r\n", line.name.c_str(), line.params.c_str());
		result += buffer;
		previousLabel = isLabel;
		disAddress += line.totalSize;
	}

	return result;
}