diff --git a/build.gradle.kts b/build.gradle.kts index 88015e1..d9d8cd1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,9 +26,10 @@ repositories { mavenCentral() } -val ghidraDir = (project.findProperty("ghidra.dir") as? String) - ?: System.getenv("GHIDRA_INSTALL_DIR") - ?: throw IllegalStateException("Can't find Ghidra installation") +val ghidraDir = + (project.findProperty("ghidra.dir") as? String) + ?: System.getenv("GHIDRA_INSTALL_DIR") + ?: throw IllegalStateException("Can't find Ghidra installation") val ghidraProps = Properties().apply { file("$ghidraDir/Ghidra/application.properties").inputStream().use { load(it) } } val ghidraVersion = ghidraProps.getProperty("application.version")!! @@ -65,19 +66,20 @@ dependencies { testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") } -val generateExtensionProps by tasks.registering() { +val generateExtensionProps by tasks.registering { val output = layout.buildDirectory.file("generated/extension.properties") outputs.file(output) doLast { file(output).outputStream().use { val props = Properties() - props += mapOf( - ("name" to "GhidraBoy"), - ("description" to "Support for Sharp SM83 / Game Boy"), - ("author" to "Gekkio"), - ("createdOn" to LocalDate.now().toString()), - ("version" to ghidraVersion) - ) + props += + mapOf( + ("name" to "GhidraBoy"), + ("description" to "Support for Sharp SM83 / Game Boy"), + ("author" to "Gekkio"), + ("createdOn" to LocalDate.now().toString()), + ("version" to ghidraVersion), + ) props.store(it, null) } } diff --git a/src/test/kotlin/fi/gekkio/ghidraboy/DisassemblyTest.kt b/src/test/kotlin/fi/gekkio/ghidraboy/DisassemblyTest.kt index 5d6a3ad..0badb05 100644 --- a/src/test/kotlin/fi/gekkio/ghidraboy/DisassemblyTest.kt +++ b/src/test/kotlin/fi/gekkio/ghidraboy/DisassemblyTest.kt @@ -757,13 +757,14 @@ class DisassemblyTest : IntegrationTest() { fun `can disassemble XOR n`() = test(0xee, "XOR 0x55", 0x55) @Test - fun `can disassemble RST 0x28`() = test(0xef, "RST 0x0028") { - val helper = EmulatorHelper(it.program) - helper.writeRegister("SP", 0xffff) - helper.step(TaskMonitor.DUMMY) - println(helper.readRegister("SP")) - println(helper.readRegister("PC")) - } + fun `can disassemble RST 0x28`() = + test(0xef, "RST 0x0028") { + val helper = EmulatorHelper(it.program) + helper.writeRegister("SP", 0xffff) + helper.step(TaskMonitor.DUMMY) + println(helper.readRegister("SP")) + println(helper.readRegister("PC")) + } @Test fun `can disassemble LDH A, (n)`() = test(0xf0, "LDH A,(0x55)", 0x55) @@ -816,7 +817,12 @@ class DisassemblyTest : IntegrationTest() { @Test fun `can disassemble RST 0x38`() = test(0xff, "RST 0x0038") - private fun test(opcode: Int, expected: String, vararg args: Int, assertions: (codeUnit: CodeUnit) -> Unit = {}) { + private fun test( + opcode: Int, + expected: String, + vararg args: Int, + assertions: (codeUnit: CodeUnit) -> Unit = {}, + ) { val codeUnit = disassemble(byteArrayOf(opcode.toByte(), *(args.map { it.toByte() }).toByteArray())) assertEquals(expected, codeUnit.toString()) assertions(codeUnit) diff --git a/src/test/kotlin/fi/gekkio/ghidraboy/GhidraApplication.kt b/src/test/kotlin/fi/gekkio/ghidraboy/GhidraApplication.kt index 92e584e..b1f2e36 100644 --- a/src/test/kotlin/fi/gekkio/ghidraboy/GhidraApplication.kt +++ b/src/test/kotlin/fi/gekkio/ghidraboy/GhidraApplication.kt @@ -28,19 +28,22 @@ class GhidraApplication : Extension { companion object { private var initialized = false - fun initialize() = synchronized(this) { - if (initialized) return - val layout = object : GhidraApplicationLayout() { - override fun findGhidraModules(): MutableMap = mutableMapOf( - "GhidraBoy" to - GModule(applicationRootDirs, ResourceFile("./")) - ).apply { - putAll(super.findGhidraModules()) - } + fun initialize() = + synchronized(this) { + if (initialized) return + val layout = + object : GhidraApplicationLayout() { + override fun findGhidraModules(): MutableMap = + mutableMapOf( + "GhidraBoy" to + GModule(applicationRootDirs, ResourceFile("./")), + ).apply { + putAll(super.findGhidraModules()) + } + } + val configuration = HeadlessGhidraApplicationConfiguration() + Application.initializeApplication(layout, configuration) + initialized = true } - val configuration = HeadlessGhidraApplicationConfiguration() - Application.initializeApplication(layout, configuration) - initialized = true - } } } diff --git a/src/test/kotlin/fi/gekkio/ghidraboy/IntegrationTest.kt b/src/test/kotlin/fi/gekkio/ghidraboy/IntegrationTest.kt index 1e3d779..9e4027e 100644 --- a/src/test/kotlin/fi/gekkio/ghidraboy/IntegrationTest.kt +++ b/src/test/kotlin/fi/gekkio/ghidraboy/IntegrationTest.kt @@ -30,9 +30,10 @@ open class IntegrationTest { open fun beforeAll() { val provider = SleighLanguageProvider.getSleighLanguageProvider() Assertions.assertFalse(provider.hadLoadFailure()) - val languageDescription = provider.languageDescriptions.filter { - it.description == "Sharp SM83" && it.processor.toString() == "SM83" - }.single() + val languageDescription = + provider.languageDescriptions.filter { + it.description == "Sharp SM83" && it.processor.toString() == "SM83" + }.single() language = provider.getLanguage(languageDescription.languageID) } diff --git a/src/test/kotlin/fi/gekkio/ghidraboy/MemoryExt.kt b/src/test/kotlin/fi/gekkio/ghidraboy/MemoryExt.kt index 63a6949..a543852 100644 --- a/src/test/kotlin/fi/gekkio/ghidraboy/MemoryExt.kt +++ b/src/test/kotlin/fi/gekkio/ghidraboy/MemoryExt.kt @@ -18,5 +18,9 @@ import ghidra.program.model.mem.Memory import ghidra.program.model.mem.MemoryBlock import ghidra.util.task.TaskMonitor -fun Memory.loadBytes(name: String, start: Address, bytes: ByteArray, overlay: Boolean = false): MemoryBlock = - createInitializedBlock(name, start, bytes.inputStream(), bytes.size.toLong(), TaskMonitor.DUMMY, overlay) +fun Memory.loadBytes( + name: String, + start: Address, + bytes: ByteArray, + overlay: Boolean = false, +): MemoryBlock = createInitializedBlock(name, start, bytes.inputStream(), bytes.size.toLong(), TaskMonitor.DUMMY, overlay) diff --git a/src/test/kotlin/fi/gekkio/ghidraboy/ProgramExt.kt b/src/test/kotlin/fi/gekkio/ghidraboy/ProgramExt.kt index 073d53e..f26b3f4 100644 --- a/src/test/kotlin/fi/gekkio/ghidraboy/ProgramExt.kt +++ b/src/test/kotlin/fi/gekkio/ghidraboy/ProgramExt.kt @@ -15,7 +15,10 @@ package fi.gekkio.ghidraboy import ghidra.program.model.listing.Program -inline fun Program.withTransaction(description: String = "", crossinline f: () -> T): T { +inline fun Program.withTransaction( + description: String = "", + crossinline f: () -> T, +): T { var success = false val id = startTransaction(description) try { diff --git a/src/test/kotlin/fi/gekkio/ghidraboy/decompiler/DecompilerTest.kt b/src/test/kotlin/fi/gekkio/ghidraboy/decompiler/DecompilerTest.kt index f7d9c70..fc90e3b 100644 --- a/src/test/kotlin/fi/gekkio/ghidraboy/decompiler/DecompilerTest.kt +++ b/src/test/kotlin/fi/gekkio/ghidraboy/decompiler/DecompilerTest.kt @@ -38,15 +38,16 @@ class DecompilerTest : IntegrationTest() { @Test fun `simple decompilation works`() { - val f = assembleFunction( - address(0x0000), - """ - LD A, 0x55 - INC A - LD (0x1234), A - RET - """.trimIndent() - ) + val f = + assembleFunction( + address(0x0000), + """ + LD A, 0x55 + INC A + LD (0x1234), A + RET + """.trimIndent(), + ) assertDecompiled( f, """ @@ -55,23 +56,24 @@ class DecompilerTest : IntegrationTest() { DAT_1234 = 0x56; return; } - """.trimIndent() + """.trimIndent(), ) } @Test fun `Github issue 10`() { - val f = assembleFunction( - address(0x0000), - """ - LD A, (0x1234) - AND A - JR NZ, 0x0009 - LD C, 0x6A - LDH (C), A - RETI - """.trimIndent() - ) + val f = + assembleFunction( + address(0x0000), + """ + LD A, (0x1234) + AND A + JR NZ, 0x0009 + LD C, 0x6A + LDH (C), A + RETI + """.trimIndent(), + ) assertDecompiled( f, """ @@ -83,31 +85,33 @@ class DecompilerTest : IntegrationTest() { IME(1); return; } - """.trimIndent() + """.trimIndent(), ) } @Test fun memcpy() { - val f = assembleFunction( - address(0x0000), - """ - LD A, B - OR C - RET Z - LD A, (DE) - LD (HL+), A - INC DE - DEC BC - JR 0x0000 - """.trimIndent(), - name = "memcpy", - params = listOf( - parameter("dst", pointer(u8), register("HL")), - parameter("src", pointer(u8), register("DE")), - parameter("len", u16, register("BC")) + val f = + assembleFunction( + address(0x0000), + """ + LD A, B + OR C + RET Z + LD A, (DE) + LD (HL+), A + INC DE + DEC BC + JR 0x0000 + """.trimIndent(), + name = "memcpy", + params = + listOf( + parameter("dst", pointer(u8), register("HL")), + parameter("src", pointer(u8), register("DE")), + parameter("len", u16, register("BC")), + ), ) - ) assertDecompiled( f, """ @@ -120,31 +124,33 @@ class DecompilerTest : IntegrationTest() { } return; } - """.trimIndent() + """.trimIndent(), ) } @Test fun memset() { - val f = assembleFunction( - address(0x0000), - """ - LD D, A - LD A, B - OR C - RET Z - LD A, D - LD (HL+), A - DEC BC - JR 0x0001 - """.trimIndent(), - name = "memset", - params = listOf( - parameter("dst", pointer(u8), register("HL")), - parameter("val", u8, register("A")), - parameter("len", u16, register("BC")) + val f = + assembleFunction( + address(0x0000), + """ + LD D, A + LD A, B + OR C + RET Z + LD A, D + LD (HL+), A + DEC BC + JR 0x0001 + """.trimIndent(), + name = "memset", + params = + listOf( + parameter("dst", pointer(u8), register("HL")), + parameter("val", u8, register("A")), + parameter("len", u16, register("BC")), + ), ) - ) assertDecompiled( f, """ @@ -156,34 +162,36 @@ class DecompilerTest : IntegrationTest() { } return; } - """.trimIndent() + """.trimIndent(), ) } @Test fun `upper nibble popcount`() { - val f = assembleFunction( - address(0x0000), - """ - LD D, A - XOR A - LD E, A - SLA D - ADC E - SLA D - ADC E - SLA D - ADC E - SLA D - ADC E - RET - """.trimIndent(), - name = "popcnt4_upper", - params = listOf( - parameter("value", u8, register("A")) - ), - returnParam = returnParameter(u8, register("A")) - ) + val f = + assembleFunction( + address(0x0000), + """ + LD D, A + XOR A + LD E, A + SLA D + ADC E + SLA D + ADC E + SLA D + ADC E + SLA D + ADC E + RET + """.trimIndent(), + name = "popcnt4_upper", + params = + listOf( + parameter("value", u8, register("A")), + ), + returnParam = returnParameter(u8, register("A")), + ) assertDecompiled( f, """ @@ -192,43 +200,44 @@ class DecompilerTest : IntegrationTest() { return ((-((char)(value << 1) >> 7) - ((char)value >> 7)) - ((char)(value << 2) >> 7)) - ((char)(value << 3) >> 7); } - """.trimIndent() + """.trimIndent(), ) } @Test fun `read_joypad_state`() { - val f = assembleFunction( - address(0x0000), - """ - LD A, 0x20 - LDH (0x00), A - LDH A, (0x00) - LDH A, (0x00) - CPL - AND 0x0F - SWAP A - LD B, A - LD A, 0x10 - LDH (0x00), A - LDH A, (0x00) - LDH A, (0x00) - LDH A, (0x00) - LDH A, (0x00) - LDH A, (0x00) - LDH A, (0x00) - CPL - AND 0x0F - OR B - LD B, A - LD A, 0x30 - LDH (0x00), A - LD A, B - RET - """.trimIndent(), - name = "read_joypad_state", - returnParam = returnParameter(u8, register("A")) - ) + val f = + assembleFunction( + address(0x0000), + """ + LD A, 0x20 + LDH (0x00), A + LDH A, (0x00) + LDH A, (0x00) + CPL + AND 0x0F + SWAP A + LD B, A + LD A, 0x10 + LDH (0x00), A + LDH A, (0x00) + LDH A, (0x00) + LDH A, (0x00) + LDH A, (0x00) + LDH A, (0x00) + LDH A, (0x00) + CPL + AND 0x0F + OR B + LD B, A + LD A, 0x30 + LDH (0x00), A + LD A, B + RET + """.trimIndent(), + name = "read_joypad_state", + returnParam = returnParameter(u8, register("A")), + ) assertDecompiled( f, """ @@ -243,34 +252,36 @@ class DecompilerTest : IntegrationTest() { P1 = 0x30; return ~bVar2 & 0xf | ~bVar1 << 4; } - """.trimIndent() + """.trimIndent(), ) } @Test fun `sla8_to_16`() { - val f = assembleFunction( - address(0x0000), - """ - LD C, A - XOR A - SLA C - RLA - SLA C - RLA - SLA C - RLA - SLA C - RLA - LD B, A - RET - """.trimIndent(), - name = "sla8_to_16", - params = listOf( - parameter("value", u8, register("A")) - ), - returnParam = returnParameter(u16, register("BC")) - ) + val f = + assembleFunction( + address(0x0000), + """ + LD C, A + XOR A + SLA C + RLA + SLA C + RLA + SLA C + RLA + SLA C + RLA + LD B, A + RET + """.trimIndent(), + name = "sla8_to_16", + params = + listOf( + parameter("value", u8, register("A")), + ), + returnParam = returnParameter(u16, register("BC")), + ) assertDecompiled( f, """ @@ -279,29 +290,31 @@ class DecompilerTest : IntegrationTest() { return CONCAT11((((value >> 7) << 1 | (byte)(value << 1) >> 7) << 1 | (byte)(value << 2) >> 7) << 1 | (byte)(value << 3) >> 7,value << 4); } - """.trimIndent() + """.trimIndent(), ) } @Test fun `DAA decompilation`() { - val f = assembleFunction( - address(0x0000), - """ - LD A, 0x01 - ADD C - DAA - LD C, A - RET Z - INC C - RET - """.trimIndent(), - name = "daa", - params = listOf( - parameter("value", u8, register("C")) - ), - returnParam = returnParameter(u8, register("C")) - ) + val f = + assembleFunction( + address(0x0000), + """ + LD A, 0x01 + ADD C + DAA + LD C, A + RET Z + INC C + RET + """.trimIndent(), + name = "daa", + params = + listOf( + parameter("value", u8, register("C")), + ), + returnParam = returnParameter(u8, register("C")), + ) assertDecompiled( f, """ @@ -316,7 +329,7 @@ class DecompilerTest : IntegrationTest() { } return bVar2 + 1; } - """.trimIndent() + """.trimIndent(), ) } @@ -353,54 +366,67 @@ class DecompilerTest : IntegrationTest() { code: String, name: String? = null, params: List? = null, - returnParam: Parameter? = null - ): Function = program.withTransaction { - val instructions: Iterable = Assemblers.getAssembler(program).assemble(address, *code.lines().toTypedArray()) - val addressSet = AddressSet() - for (instruction in instructions) { - addressSet.add(instruction.minAddress, instruction.maxAddress) - } - program.functionManager.createFunction(name, address, addressSet, SourceType.USER_DEFINED).apply { - setCustomVariableStorage(true) - val callingConvention = "default" - val force = true - if (params != null) { - updateFunction( - callingConvention, - returnParam, - params, - Function.FunctionUpdateType.CUSTOM_STORAGE, - force, - SourceType.USER_DEFINED - ) - } else { - updateFunction( - callingConvention, - returnParam, - Function.FunctionUpdateType.CUSTOM_STORAGE, - force, - SourceType.USER_DEFINED - ) + returnParam: Parameter? = null, + ): Function = + program.withTransaction { + val instructions: Iterable = Assemblers.getAssembler(program).assemble(address, *code.lines().toTypedArray()) + val addressSet = AddressSet() + for (instruction in instructions) { + addressSet.add(instruction.minAddress, instruction.maxAddress) + } + program.functionManager.createFunction(name, address, addressSet, SourceType.USER_DEFINED).apply { + setCustomVariableStorage(true) + val callingConvention = "default" + val force = true + if (params != null) { + updateFunction( + callingConvention, + returnParam, + params, + Function.FunctionUpdateType.CUSTOM_STORAGE, + force, + SourceType.USER_DEFINED, + ) + } else { + updateFunction( + callingConvention, + returnParam, + Function.FunctionUpdateType.CUSTOM_STORAGE, + force, + SourceType.USER_DEFINED, + ) + } } } - } private fun decompile(function: Function) = decompiler.decompileFunction(function, 10, TaskMonitor.DUMMY).also { assertTrue(it.decompileCompleted()) { "Decompilation did not complete" } }.decompiledFunction.c - private fun formatCode(code: String) = code.lineSequence() - .map { it.trim() } - .filter { it.isNotEmpty() } - .joinToString(separator = "\n") - private fun assertDecompiled(function: Function, @Language("C") code: String) = - assertEquals(formatCode(code), formatCode(decompile(function))) + private fun formatCode(code: String) = + code.lineSequence() + .map { it.trim() } + .filter { it.isNotEmpty() } + .joinToString(separator = "\n") + + private fun assertDecompiled( + function: Function, + @Language("C") code: String, + ) = assertEquals(formatCode(code), formatCode(decompile(function))) + + private fun parameter( + name: String, + type: DataType, + register: Register, + ) = ParameterImpl(name, type, register, program) + + private fun returnParameter( + type: DataType, + register: Register, + ) = ReturnParameterImpl(type, register, program) - private fun parameter(name: String, type: DataType, register: Register) = - ParameterImpl(name, type, register, program) - private fun returnParameter(type: DataType, register: Register) = - ReturnParameterImpl(type, register, program) private fun pointer(type: DataType) = PointerDataType(type) + private fun register(name: String) = program.getRegister(name) } diff --git a/src/test/kotlin/fi/gekkio/ghidraboy/emu/ControlFlowInstructionTest.kt b/src/test/kotlin/fi/gekkio/ghidraboy/emu/ControlFlowInstructionTest.kt index 1d31651..7ab35cc 100644 --- a/src/test/kotlin/fi/gekkio/ghidraboy/emu/ControlFlowInstructionTest.kt +++ b/src/test/kotlin/fi/gekkio/ghidraboy/emu/ControlFlowInstructionTest.kt @@ -42,7 +42,10 @@ class ControlFlowInstructionTest : EmuTest() { @ParameterizedTest @ArgumentsSource(Conditions::class) - fun `JP cc, nn`(cc: Condition, taken: Boolean) { + fun `JP cc, nn`( + cc: Condition, + taken: Boolean, + ) { emulator.writeF((if (taken) cc else cc.flip()).testFlags) emulator.write(0x000u, cc.jpOpcode, 0x34u, 0x12u) emulator.step() @@ -69,7 +72,10 @@ class ControlFlowInstructionTest : EmuTest() { @ParameterizedTest @ArgumentsSource(Conditions::class) - fun `JR cc, e (positive)`(cc: Condition, taken: Boolean) { + fun `JR cc, e (positive)`( + cc: Condition, + taken: Boolean, + ) { emulator.writeF((if (taken) cc else cc.flip()).testFlags) emulator.write(0x000u, cc.jrOpcode, 0x7fu) emulator.step() @@ -82,7 +88,10 @@ class ControlFlowInstructionTest : EmuTest() { @ParameterizedTest @ArgumentsSource(Conditions::class) - fun `JR cc, e (negative)`(cc: Condition, taken: Boolean) { + fun `JR cc, e (negative)`( + cc: Condition, + taken: Boolean, + ) { emulator.writeF((if (taken) cc else cc.flip()).testFlags) emulator.write(0x000u, cc.jrOpcode, 0x80u) emulator.step() @@ -108,7 +117,10 @@ class ControlFlowInstructionTest : EmuTest() { @ParameterizedTest @ArgumentsSource(Conditions::class) - fun `CALL cc, nn`(cc: Condition, taken: Boolean) { + fun `CALL cc, nn`( + cc: Condition, + taken: Boolean, + ) { emulator.writePC(0xabcdu) emulator.writeSP(0xbfffu) emulator.writeF((if (taken) cc else cc.flip()).testFlags) @@ -139,7 +151,10 @@ class ControlFlowInstructionTest : EmuTest() { @ParameterizedTest @ArgumentsSource(Conditions::class) - fun `RET cc, nn`(cc: Condition, taken: Boolean) { + fun `RET cc, nn`( + cc: Condition, + taken: Boolean, + ) { emulator.writePC(0x1234u) emulator.writeSP(0xbffdu) emulator.writeF((if (taken) cc else cc.flip()).testFlags) @@ -197,7 +212,7 @@ enum class Rst(val opcode: UByte, val target: UShort) { Rst20(0xe7u, 0x0020u), Rst28(0xefu, 0x0028u), Rst30(0xf7u, 0x0030u), - Rst38(0xffu, 0x0038u) + Rst38(0xffu, 0x0038u), } enum class Condition( @@ -205,17 +220,19 @@ enum class Condition( val jrOpcode: UByte, val jpOpcode: UByte, val callOpcode: UByte, - val retOpcode: UByte + val retOpcode: UByte, ) { NC(0b1110_0000u, 0x30u, 0xd2u, 0xd4u, 0xd0u), C(0b0001_0000u, 0x38u, 0xdau, 0xdcu, 0xd8u), NZ(0b0111_0000u, 0x20u, 0xc2u, 0xc4u, 0xc0u), - Z(0b1000_0000u, 0x28u, 0xcau, 0xccu, 0xc8u); - - fun flip(): Condition = when (this) { - NC -> C - C -> NC - NZ -> Z - Z -> NZ - } + Z(0b1000_0000u, 0x28u, 0xcau, 0xccu, 0xc8u), + ; + + fun flip(): Condition = + when (this) { + NC -> C + C -> NC + NZ -> Z + Z -> NZ + } } diff --git a/src/test/kotlin/fi/gekkio/ghidraboy/emu/EmulatorExt.kt b/src/test/kotlin/fi/gekkio/ghidraboy/emu/EmulatorExt.kt index 87930b0..ec67a3b 100644 --- a/src/test/kotlin/fi/gekkio/ghidraboy/emu/EmulatorExt.kt +++ b/src/test/kotlin/fi/gekkio/ghidraboy/emu/EmulatorExt.kt @@ -25,61 +25,108 @@ fun EmulatorHelper.step() { } } -fun EmulatorHelper.write(address: UShort, vararg bytes: UByte) = writeMemory( +fun EmulatorHelper.write( + address: UShort, + vararg bytes: UByte, +) = writeMemory( language.addressFactory.defaultAddressSpace.getAddress(address.toLong()), - bytes.map { it.toByte() }.toByteArray() + bytes.map { it.toByte() }.toByteArray(), ) -fun EmulatorHelper.read(address: UShort): UByte = readMemoryByte( - language.addressFactory.defaultAddressSpace.getAddress(address.toLong()) -).toUByte() +fun EmulatorHelper.read(address: UShort): UByte = + readMemoryByte( + language.addressFactory.defaultAddressSpace.getAddress(address.toLong()), + ).toUByte() -fun EmulatorHelper.read(address: UShort, length: Int): UByteArray = readMemory( - language.addressFactory.defaultAddressSpace.getAddress(address.toLong()), - length -).map { it.toUByte() }.toUByteArray() +fun EmulatorHelper.read( + address: UShort, + length: Int, +): UByteArray = + readMemory( + language.addressFactory.defaultAddressSpace.getAddress(address.toLong()), + length, + ).map { it.toUByte() }.toUByteArray() fun EmulatorHelper.assertA(a: UByte) = assertEquals(a, readA()) + fun EmulatorHelper.assertF(f: UByte) = assertEquals(f, readF()) + fun EmulatorHelper.assertB(b: UByte) = assertEquals(b, readB()) + fun EmulatorHelper.assertC(c: UByte) = assertEquals(c, readC()) + fun EmulatorHelper.assertD(d: UByte) = assertEquals(d, readD()) + fun EmulatorHelper.assertE(e: UByte) = assertEquals(e, readE()) + fun EmulatorHelper.assertH(h: UByte) = assertEquals(h, readH()) + fun EmulatorHelper.assertL(l: UByte) = assertEquals(l, readL()) + fun EmulatorHelper.assertAF(af: UShort) = assertEquals(af, readAF()) + fun EmulatorHelper.assertBC(bc: UShort) = assertEquals(bc, readBC()) + fun EmulatorHelper.assertDE(de: UShort) = assertEquals(de, readDE()) + fun EmulatorHelper.assertHL(hl: UShort) = assertEquals(hl, readHL()) + fun EmulatorHelper.assertPC(pc: UShort) = assertEquals(pc, readPC()) + fun EmulatorHelper.assertSP(sp: UShort) = assertEquals(sp, readSP()) fun EmulatorHelper.readA(): UByte = this.readRegister("A").toInt().toUByte() + fun EmulatorHelper.readF(): UByte = this.readRegister("F").toInt().toUByte() + fun EmulatorHelper.readB(): UByte = this.readRegister("B").toInt().toUByte() + fun EmulatorHelper.readC(): UByte = this.readRegister("C").toInt().toUByte() + fun EmulatorHelper.readD(): UByte = this.readRegister("D").toInt().toUByte() + fun EmulatorHelper.readE(): UByte = this.readRegister("E").toInt().toUByte() + fun EmulatorHelper.readH(): UByte = this.readRegister("H").toInt().toUByte() + fun EmulatorHelper.readL(): UByte = this.readRegister("L").toInt().toUByte() + fun EmulatorHelper.readAF(): UShort = this.readRegister("AF").toInt().toUShort() + fun EmulatorHelper.readBC(): UShort = this.readRegister("BC").toInt().toUShort() + fun EmulatorHelper.readDE(): UShort = this.readRegister("DE").toInt().toUShort() + fun EmulatorHelper.readHL(): UShort = this.readRegister("HL").toInt().toUShort() + fun EmulatorHelper.readPC(): UShort = this.readRegister("PC").toInt().toUShort() + fun EmulatorHelper.readSP(): UShort = this.readRegister("SP").toInt().toUShort() fun EmulatorHelper.writeA(a: UByte) = this.writeRegister("A", a.toLong()) + fun EmulatorHelper.writeF(f: UByte) = this.writeRegister("F", f.toLong()) + fun EmulatorHelper.writeB(b: UByte) = this.writeRegister("B", b.toLong()) + fun EmulatorHelper.writeC(c: UByte) = this.writeRegister("C", c.toLong()) + fun EmulatorHelper.writeD(d: UByte) = this.writeRegister("D", d.toLong()) + fun EmulatorHelper.writeE(e: UByte) = this.writeRegister("E", e.toLong()) + fun EmulatorHelper.writeH(h: UByte) = this.writeRegister("H", h.toLong()) + fun EmulatorHelper.writeL(l: UByte) = this.writeRegister("L", l.toLong()) + fun EmulatorHelper.writeAF(af: UShort) = this.writeRegister("AF", af.toLong()) + fun EmulatorHelper.writeBC(bc: UShort) = this.writeRegister("BC", bc.toLong()) + fun EmulatorHelper.writeDE(de: UShort) = this.writeRegister("DE", de.toLong()) + fun EmulatorHelper.writeHL(hl: UShort) = this.writeRegister("HL", hl.toLong()) + fun EmulatorHelper.writePC(pc: UShort) = this.writeRegister("PC", pc.toLong()) + fun EmulatorHelper.writeSP(sp: UShort) = this.writeRegister("SP", sp.toLong()) diff --git a/src/test/kotlin/fi/gekkio/ghidraboy/emu/FailOnMemoryFault.kt b/src/test/kotlin/fi/gekkio/ghidraboy/emu/FailOnMemoryFault.kt index 2bbf302..815c41f 100644 --- a/src/test/kotlin/fi/gekkio/ghidraboy/emu/FailOnMemoryFault.kt +++ b/src/test/kotlin/fi/gekkio/ghidraboy/emu/FailOnMemoryFault.kt @@ -20,7 +20,12 @@ import ghidra.program.model.address.Address import org.junit.jupiter.api.Assertions class FailOnMemoryFault(private val emulator: EmulatorHelper) : MemoryFaultHandler { - override fun uninitializedRead(address: Address, size: Int, buf: ByteArray, bufOffset: Int): Boolean { + override fun uninitializedRead( + address: Address, + size: Int, + buf: ByteArray, + bufOffset: Int, + ): Boolean { if (emulator.emulateExecutionState == EmulateExecutionState.INSTRUCTION_DECODE) { return false } @@ -32,7 +37,10 @@ class FailOnMemoryFault(private val emulator: EmulatorHelper) : MemoryFaultHandl return true } - override fun unknownAddress(address: Address, write: Boolean): Boolean { + override fun unknownAddress( + address: Address, + write: Boolean, + ): Boolean { val pc = emulator.executionAddress if (write) { Assertions.fail("Unknown address written at $pc: $address")