package com.smallhacker.disbrowser.disassembler import com.smallhacker.disbrowser.asm.* import com.smallhacker.disbrowser.game.* import com.smallhacker.disbrowser.util.LifoQueue import com.smallhacker.disbrowser.util.mutableMultiMap import com.smallhacker.disbrowser.util.putSingle import kotlin.collections.ArrayList import kotlin.collections.HashMap object Disassembler { fun disassemble(initialState: State, gameData: GameData, global: Boolean): Disassembly { val seen = HashSet() val queue = LifoQueue() val origins = mutableMultiMap() val instructionMap = HashMap() fun tryAdd(state: State, origin: SnesAddress?) { if (origin != null) { origins.putSingle(state.address, origin) } if (seen.add(state.address)) { queue.add(state) } } tryAdd(initialState, null) val instructions = ArrayList() while (queue.isNotEmpty()) { val state = queue.removeNext() val ins = disassembleInstruction(state) instructions.add(ins) instructionMap[ins.address] = ins if (ins.opcode.mode.instructionLength(state) == null) { ins.continuation = Continuation.INSUFFICIENT_DATA } val linkedState = ins.linkedState val localAddress = ins.address val remoteAddress = linkedState?.address val localFlags = gameData.flagsAt(localAddress) val remoteFlags = gameData.flagsAt(remoteAddress) val pointerTableEntries = localFlags.findFlag()?.entries localFlags.forFlag { ins.continuation = Continuation.STOP if (global) { readTable(state.memory) .filterNotNull() .map { ins.postState.copy(address = it) } .forEach { tryAdd(it, ins.address) } } generatePointerTable(ins).forEach { instructions.add(it) } } remoteFlags.forFlag { ins.continuation = Continuation.STOP if (pointerTableEntries != null) { if (global) { readTable(ins.postState, pointerTableEntries) .filterNotNull() .map { ins.postState.copy(address = it) } .forEach { tryAdd(it, ins.address) } } } generatePointerTable(ins, pointerTableEntries?.toUInt()).forEach { instructions.add(it) } } remoteFlags.forFlag { ins.continuation = Continuation.STOP } if (!ins.continuation.shouldStop) { tryAdd(ins.postState, ins.address) } if (linkedState != null) { if (ins.opcode.branch || global) { tryAdd(linkedState, ins.address) } } } val fatalSeen = HashSet() val fatalQueue = LifoQueue() fun tryAddFatal(snesAddress: SnesAddress) { if (fatalSeen.add(snesAddress)) { fatalQueue.add(snesAddress) } } instructions.asSequence() .filterIsInstance() .filter { it.continuation == Continuation.FATAL_ERROR } .forEach { tryAddFatal(it.address) } while (fatalQueue.isNotEmpty()) { val badAddress = fatalQueue.removeNext()!! val instruction = instructionMap[badAddress] ?: continue val mnemonic = instruction.opcode.mnemonic if (mnemonic == Mnemonic.JSL || mnemonic == Mnemonic.JSR) continue instruction.certainty = Certainty.PROBABLY_WRONG origins[badAddress]?.forEach{tryAddFatal(it)} } val instructionList = instructions .sortedBy { it.sortedAddress } .toList() return Disassembly(instructionList) } fun disassembleSegments(initialState: State): List { val mapping = HashMap() val queue = LifoQueue() val segments = ArrayList() fun tryAdd(state: State) { if (!mapping.containsKey(state.address)) { queue.add(state) } } tryAdd(initialState) while (queue.isNotEmpty()) { val state = queue.removeNext() if (mapping.containsKey(state.address)) { continue } val segment = disassembleSegment(state, mapping) if (segment.instructions.isEmpty()) { continue } segments.add(segment) val end = segment.end end.local.forEach { queue.add(it) } end.remote.forEach { queue.add(it) } } return segments } fun disassembleSegment(initialState: State, mapping: MutableMap): Segment { val instructions = ArrayList() var lastState = initialState val queue = LifoQueue() val seen = HashSet() fun finalize(segment: Segment): Segment { instructions.forEach { mapping[it.address] = segment } return segment } fun tryAdd(state: State) { if (seen.add(state.address)) { queue.add(state) } } tryAdd(initialState) while (queue.isNotEmpty()) { val state = queue.removeNext() val ins = disassembleInstruction(state) instructions.add(ins) //println(ins) val segmentEnd = ins.opcode.ender(ins) if (segmentEnd != null) { return finalize(Segment(initialState.address, segmentEnd, instructions)) } tryAdd(ins.postState) lastState = ins.postState } return finalize(Segment(initialState.address, continuationSegmentEnd(lastState), instructions)) } private fun disassembleInstruction(state: State): MutableInstruction { val opcodeValue = state.memory[state.address] ?: return unreadableInstruction(state) val opcode = Opcode.opcode(opcodeValue) val length = opcode.mode.instructionLength(state) ?: 1u val bytes = state.memory.range(state.address.value.toUInt(), length).validate() ?: return unreadableInstruction(state) val continuation = opcode.continuation val certainty = Certainty.PROBABLY_CORRECT return MutableInstruction(bytes, opcode, state, continuation, certainty) } private fun unreadableInstruction(state: State) = MutableInstruction(EmptyMemorySpace, Opcode.UNKNOWN_OPCODE, state, Continuation.INSUFFICIENT_DATA, Certainty.PROBABLY_WRONG) }