diff --git a/SwiftSplit.xcodeproj/project.pbxproj b/SwiftSplit.xcodeproj/project.pbxproj index acfa9d7..f33676a 100644 --- a/SwiftSplit.xcodeproj/project.pbxproj +++ b/SwiftSplit.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + CD2A0F532697E10100863275 /* RemoteMemoryAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD2A0F522697E10100863275 /* RemoteMemoryAccess.swift */; }; CD5E946B267A52B900CFAB31 /* EventStreamItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD5E9469267A52B900CFAB31 /* EventStreamItem.swift */; }; CD5E946C267A52B900CFAB31 /* EventStreamItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = CD5E946A267A52B900CFAB31 /* EventStreamItem.xib */; }; CD5E946E267A5ED300CFAB31 /* EventListDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD5E946D267A5ED300CFAB31 /* EventListDelegate.swift */; }; @@ -21,11 +22,13 @@ CD7CDC05256F7304002AE46F /* SimpleWebSocketServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7CDC04256F7304002AE46F /* SimpleWebSocketServer.swift */; }; CD7CDC2825717D2D002AE46F /* CelesteSplitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7CDC2725717D2D002AE46F /* CelesteSplitter.swift */; }; CDA94457257382D300DB2A85 /* RouteBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA94456257382D300DB2A85 /* RouteBox.swift */; }; + CDC82AB926981C4E007436BD /* Mono.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC82AB826981C4E007436BD /* Mono.swift */; }; DB56D9F05D0BC5B4DE4CECC8 /* Pods_SwiftSplit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF09341B2ECF29501B823DEC /* Pods_SwiftSplit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2557401E37458142957F7D9D /* Pods-SwiftSplit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftSplit.debug.xcconfig"; path = "Target Support Files/Pods-SwiftSplit/Pods-SwiftSplit.debug.xcconfig"; sourceTree = ""; }; + CD2A0F522697E10100863275 /* RemoteMemoryAccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteMemoryAccess.swift; sourceTree = ""; }; CD5E9469267A52B900CFAB31 /* EventStreamItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventStreamItem.swift; sourceTree = ""; }; CD5E946A267A52B900CFAB31 /* EventStreamItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EventStreamItem.xib; sourceTree = ""; }; CD5E946D267A5ED300CFAB31 /* EventListDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventListDelegate.swift; sourceTree = ""; }; @@ -46,6 +49,7 @@ CD7CDC04256F7304002AE46F /* SimpleWebSocketServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleWebSocketServer.swift; sourceTree = ""; }; CD7CDC2725717D2D002AE46F /* CelesteSplitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CelesteSplitter.swift; sourceTree = ""; }; CDA94456257382D300DB2A85 /* RouteBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteBox.swift; sourceTree = ""; }; + CDC82AB826981C4E007436BD /* Mono.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mono.swift; sourceTree = ""; }; CF09341B2ECF29501B823DEC /* Pods_SwiftSplit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftSplit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E1C15A185F4552F09EFE4D9D /* Pods-SwiftSplit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftSplit.release.xcconfig"; path = "Target Support Files/Pods-SwiftSplit/Pods-SwiftSplit.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -105,6 +109,7 @@ CD7CDBE6256F5D0D002AE46F /* ViewController.swift */, CD5E946D267A5ED300CFAB31 /* EventListDelegate.swift */, CD7CDC2725717D2D002AE46F /* CelesteSplitter.swift */, + CD7CDBF9256F5DFC002AE46F /* CelesteScanner.swift */, CD5E9469267A52B900CFAB31 /* EventStreamItem.swift */, CD5E946A267A52B900CFAB31 /* EventStreamItem.xib */, CD7CDBE8256F5D0F002AE46F /* Assets.xcassets */, @@ -114,7 +119,6 @@ CD7CDBEE256F5D0F002AE46F /* SwiftSplit.entitlements */, CD7CDBF4256F5DDB002AE46F /* SwiftSplit-Bridging-Header.h */, CD7CDBFC256F5DFC002AE46F /* CelesteMemoryLayout.md */, - CD7CDBF9256F5DFC002AE46F /* CelesteScanner.swift */, ); path = SwiftSplit; sourceTree = ""; @@ -125,6 +129,8 @@ CD7CDBFD256F5DFC002AE46F /* memory_scanner.c */, CD7CDBFA256F5DFC002AE46F /* memory_scanner.h */, CD7CDBF8256F5DFC002AE46F /* MemoryScanner.swift */, + CD2A0F522697E10100863275 /* RemoteMemoryAccess.swift */, + CDC82AB826981C4E007436BD /* Mono.swift */, CD7CDC02256F626D002AE46F /* LiveSplitServer.swift */, CD7CDC04256F7304002AE46F /* SimpleWebSocketServer.swift */, ); @@ -249,11 +255,13 @@ files = ( CD7CDC03256F626D002AE46F /* LiveSplitServer.swift in Sources */, CD7CDBFE256F5DFC002AE46F /* MemoryScanner.swift in Sources */, + CDC82AB926981C4E007436BD /* Mono.swift in Sources */, CD7CDC00256F5DFC002AE46F /* memory_scanner.c in Sources */, CD7CDBE7256F5D0D002AE46F /* ViewController.swift in Sources */, CD5E946E267A5ED300CFAB31 /* EventListDelegate.swift in Sources */, CDA94457257382D300DB2A85 /* RouteBox.swift in Sources */, CD7CDBFF256F5DFC002AE46F /* CelesteScanner.swift in Sources */, + CD2A0F532697E10100863275 /* RemoteMemoryAccess.swift in Sources */, CD7CDC05256F7304002AE46F /* SimpleWebSocketServer.swift in Sources */, CD5E946B267A52B900CFAB31 /* EventStreamItem.swift in Sources */, CD7CDBE5256F5D0D002AE46F /* AppDelegate.swift in Sources */, diff --git a/SwiftSplit/CelesteScanner.swift b/SwiftSplit/CelesteScanner.swift index 6965b50..334dd42 100644 --- a/SwiftSplit/CelesteScanner.swift +++ b/SwiftSplit/CelesteScanner.swift @@ -13,7 +13,7 @@ import Foundation */ class CelesteScanner { var enableDebugPrinting: Bool = false - + var pid: pid_t var headerInfo: HeaderInfo? = nil { didSet { @@ -25,141 +25,100 @@ class CelesteScanner { } } var headerSignature: MemscanSignature? = nil - var pointer: vm_address_t = 0 + var autoSplitterInfo: RmaPointer? = nil + var extendedInfo: RmaPointer? = nil let target: MemscanTarget - let filter: MemscanFilter + let process: RmaProcess init(pid: pid_t) throws { self.pid = pid target = try MemscanTarget(pid: pid) - filter = MemscanFilter(startAddress: 0, endAddress: 0x0000800000000000) + process = RmaProcess(target: target, filter: MemscanFilter(startAddress: 0, endAddress: 0x0000001000000000)) } - + func findHeader() throws { print("Scanning for the AutoSplitterData object header") - + + extendedInfo = try process.findPointer(by: "11efbeadde11") + if CelesteScanner.canImmediatelyConnect(pid: pid) { headerInfo = CelesteScanner.lastHeader + try? debugPointers() return } - let bigboisignature = MemscanSignature(parsing: - "7f00000000000000000000????????????????ffffffffffffffff000000000000000000000" + - "000000000000000000000000000000000000000000000000000000000000000000000000000" - ) - let scanner = MemscanScanner(target: target, signature: bigboisignature, filter: filter) - while let match = try scanner.next() { - let address = match.address - 5 - print(String(format: " Found a candiate object at %016llx", address)) - let data = try target.read(at: address, count: 16) - let info = HeaderInfo(pid: pid, signatureData: Array(data.buffer.bindMemory(to: UInt8.self)), header: try readData(at: address).header) - headerInfo = info + autoSplitterInfo = try process.findPointer(by: + "7f00000000000000000000????????????????ffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )?.offset(by: -5) + if let info = autoSplitterInfo { + headerInfo = HeaderInfo(pid: pid, signatureData: try info.read(bytes: 16)) CelesteScanner.lastHeader = headerInfo - return } + } - - func readData(at pointer: vm_address_t) throws -> AutoSplitterData { - let buf = try target.read(at: pointer, count: AutoSplitterData.STRUCT_SIZE) - var data = AutoSplitterData(from: buf.buffer.baseAddress!) - data.location = pointer - return data - } - - func findData() throws { - print("Searching for the AutoSplitterInfo instance") - guard let signature = headerSignature else { - print(" There is no header signature. Please launch the game fresh and scan for the header.") - pointer = 0 - return - } - let scanner = MemscanScanner(target: target, signature: signature, filter: filter) - - if let match = try scanner.next() { - self.pointer = match.address - print(String(format: " Found the instance at %llx", match.address)) - } else { - pointer = 0 - } + func readExtended() throws -> ExtendedAutoSplitterData? { +// if guard let extendedInfo = extendedInfo else { +// return nil +// } +// let buf = try target.read(at: extendedPointer, count: ) +// return ExtendedAutoSplitterData(from: extendedInfo) + return nil } - + func getInfo() throws -> AutoSplitterInfo? { - if pointer == 0 { try findData() } - if pointer == 0 { return nil } - guard let headerInfo = self.headerInfo else { return nil } - var data = try readData(at: pointer) - if data.header != headerInfo.header || data.headerPad != 0 { - try findData() - if pointer == 0 { return nil } - data = try readData(at: pointer) + guard let header = self.headerInfo else { return nil } + if try autoSplitterInfo?.read(bytes: 16) != header.signatureData { + autoSplitterInfo = try process.findPointer(by: MemscanSignature(from: header.signatureData)) } - return try AutoSplitterInfo(from: data, in: target) + guard let info = autoSplitterInfo else { return nil } + return try AutoSplitterInfo(from: try AutoSplitterData(from: info)) } - - /** - Run a scan for the signature, printing the memory of every match. Useful for figuring out the memory layout - */ - func debugScan() throws { - print("Running a debug scan") - let fileTimeSignature = MemscanSignature(parsing: "11addeefbeadde11") - - let scanner = MemscanScanner(target: target, signature: fileTimeSignature, filter: filter) - while let match = try scanner.next() { - print(String(format:" Found a candidate at %llx", match.address)) - try debugMemoryAround(match.address, before: 64, after: 64) - } - } - + /** Run a scan for the level strings, printing the memory of every match. Useful for figuring out the memory layout */ func debugPointers() throws { - - print("Running a pointer debug") - guard let signature = headerSignature else { - print(" There is no header signature. Please open the AUTOSPLIT save and scan for the header.") - pointer = 0 - return - } - let scanner = MemscanScanner(target: target, signature: signature, filter: filter) - while let match = try scanner.next() { - print(String(format: " Found an instance at %016llx", match.address)) - let data = try readData(at: match.address) - print(String(format: " Level string points to %016llx", data.levelPointer)) - try debugMemoryAround(data.levelPointer, before: 16, after: 64) - - let vTable = try readPointer(from: match.address, offset: 0) - print(String(format: " Object header points to %016llx", vTable)) - try debugMemoryAround(vTable, before: 0, after: 64) - - let classData = try readPointer(from: vTable, offset: 24) - print(String(format: " Static class data at %016llx", classData)) - try debugMemoryAround(classData, before: 0, after: 64) - - let monoClass = try readPointer(from: vTable, offset: 0) - print(String(format: " MonoClass at %016llx", monoClass)) - try debugMemoryAround(monoClass, before: 0, after: 64) - - let monoClassName = try readPointer(from: monoClass, offset: 8) - print(String(format: " MonoClass name at %016llx", monoClassName)) - try debugMemoryAround(monoClassName, before: 0, after: 64) - - } +// print("Running a pointer debug") +// guard let signature = headerSignature else { +// print(" There is no header signature. Please open the AUTOSPLIT save and scan for the header.") +// pointer = 0 +// return +// } +// +// let scanner = MemscanScanner(target: target, signature: signature, filter: filter) +// while let match = try scanner.next() { +// print(String(format: "Object @ %016llx", match.address)) +// +// let vTable_ptr = try readPointer(from: match.address, offset: 0) +// print(String(format: "VTable @ %016llx", vTable_ptr)) +// +//// try debugMemoryAround(vTable_ptr, before: 0, after: 64) +// +// let monoClass_ptr = try readPointer(from: vTable_ptr, offset: 0) +// print(String(format: "MonoClass @ %016llx", monoClass_ptr)) +// +//// try debugMemoryAround(monoClass_ptr, before: 0, after: 64) +// +// let name_ptr = try readPointer(from: monoClass_ptr, offset: 8) +// print(String(format: "MonoClass.name @ %016llx", name_ptr)) +// try debugMemoryAround(name_ptr, before: 0, after: 128) +// +// } } - + func readPointer(from address: vm_address_t, offset: vm_offset_t) throws -> vm_address_t { let data = try target.read(at: address + offset, count: 8) return data.buffer.bindMemory(to: vm_address_t.self)[0] } - + func debugMemoryAround(_ address: vm_address_t, before: vm_offset_t, after: vm_offset_t) throws { let data = try target.read(at: address - before, count: before + after) print(" Forward: \(data.debugString(withCursor: Int(before)))") print(" Reversed: \(data.debugStringReversed(withCursor: Int(before)))") } - + static var lastHeader: HeaderInfo? { get { if let data = UserDefaults.standard.value(forKey: "lastHeader") as? Data, @@ -176,7 +135,7 @@ class CelesteScanner { } } } - + static func canImmediatelyConnect(pid: pid_t) -> Bool { return CelesteScanner.lastHeader?.pid == pid } @@ -185,18 +144,16 @@ class CelesteScanner { struct HeaderInfo: Codable { var pid: pid_t var signatureData: [UInt8] - var header: UInt64 - init(pid: pid_t, signatureData: [UInt8], header: UInt64) { + init(pid: pid_t, signatureData: [UInt8]) { self.pid = pid self.signatureData = signatureData - self.header = header } } /** A representation of the C# AutoSplitterInfo memory. - + The C# AutoSplitterInfo layout is as follows:: ``` Placeholders: @@ -217,82 +174,56 @@ struct HeaderInfo: Codable { FileHearts = 0x1ffffff1 } ``` - + ``` 10b212968c7f0000 00000000 00000000 b83c632d01000000 f1aaaa1f f1bbbb1f 01010100 00000000 - placeholders --------######## -------- ######## --------######## -------- ######## -------- ######## 0 4 8 12 16 20 24 28 32 36 10e28b84a07f0000 00000000 00000000 b009150f01000000 ffffffff ffffffff 00000000 00000000 - Fresh launch state - + 11ffeeddccbbaa11 f1cccc1f 01010000 11addeefbeadde11 f1dddd1f f1eeee1f f1ffff1f 00000000 - placeholders --------######## -------- ######## --------######## -------- ######## -------- ######## 40 44 48 52 56 60 64 68 72 76 0000000000000000 00000000 00000000 0000000000000000 00000000 00000000 00000000 00000000 - Fresh launch state ``` */ -struct AutoSplitterData: Equatable { - var location: vm_address_t = 0 - var header: UInt64 = 0 - var headerPad: UInt64 = 0 - - var chapter: Int32 = 0 - var mode: Int32 = 0 - var levelPointer: vm_address_t = 0 - var timerActive: UInt8 = 0 - var chapterStarted: UInt8 = 0 - var chapterComplete: UInt8 = 0 - var chapterTime: Int64 = 0 - var chapterStrawberries: Int32 = 0 - var chapterCassette: UInt8 = 0 - var chapterHeart: UInt8 = 0 - var fileTime: Int64 = 0 - var fileStrawberries: Int32 = 0 - var fileCassettes: Int32 = 0 - var fileHearts: Int32 = 0 +struct AutoSplitterData { + var header: UInt64 + var monoSync: UInt64 - init() {} - - init(from pointer: UnsafeRawPointer) { - header = pointer.load(fromByteOffset: 0, as: UInt64.self) - headerPad = pointer.load(fromByteOffset: 8, as: UInt64.self) - levelPointer = pointer.load(fromByteOffset: 16, as: vm_address_t.self) - chapter = pointer.load(fromByteOffset: 24, as: Int32.self) - mode = pointer.load(fromByteOffset: 28, as: Int32.self) - timerActive = pointer.load(fromByteOffset: 32, as: UInt8.self) - chapterStarted = pointer.load(fromByteOffset: 33, as: UInt8.self) - chapterComplete = pointer.load(fromByteOffset: 34, as: UInt8.self) - chapterTime = pointer.load(fromByteOffset: 40, as: Int64.self) - chapterStrawberries = pointer.load(fromByteOffset: 48, as: Int32.self) - chapterCassette = pointer.load(fromByteOffset: 52, as: UInt8.self) - chapterHeart = pointer.load(fromByteOffset: 53, as: UInt8.self) - fileTime = pointer.load(fromByteOffset: 56, as: Int64.self) - fileStrawberries = pointer.load(fromByteOffset: 64, as: Int32.self) - fileCassettes = pointer.load(fromByteOffset: 68, as: Int32.self) - fileHearts = pointer.load(fromByteOffset: 72, as: Int32.self) - } - - static let FILE_TIME_OFFSET: vm_offset_t = 56 - static let STRUCT_SIZE: vm_offset_t = 80 - - /** - Parse a C# string object - - Memory layout: - ``` - "NUM:0" - ``` - ``` - header length "N U M : 0 " - a8cd0054c57f0000 0000000000000000 05000000 4e00 5500 4d00 3a00 3000 - 0 8 16 20 - ``` - */ - static func parseCSharpString(at address: vm_address_t, target: MemscanTarget) throws -> String { - let lengthData = try target.read(at: address + 16, count: 4) - let length = lengthData.buffer.load(as: Int32.self) - - let stringData = try target.read(at: address + 20, count: vm_offset_t(length) * 2) - return String(utf16CodeUnits: stringData.buffer.bindMemory(to: unichar.self).baseAddress!, count: Int(length)) + var chapter: Int32 + var mode: Int32 + var level: RmaPointer + var timerActive: UInt8 + var chapterStarted: UInt8 + var chapterComplete: UInt8 + var chapterTime: Int64 + var chapterStrawberries: Int32 + var chapterCassette: UInt8 + var chapterHeart: UInt8 + var fileTime: Int64 + var fileStrawberries: Int32 + var fileCassettes: Int32 + var fileHearts: Int32 + + init(from pointer: RmaPointer) throws { + let preload = try pointer.preload(size: 80) + header = preload.value(at: 0) + monoSync = preload.value(at: 8) + level = preload.value(at: 16) + chapter = preload.value(at: 24) + mode = preload.value(at: 28) + timerActive = preload.value(at: 32) + chapterStarted = preload.value(at: 33) + chapterComplete = preload.value(at: 34) + chapterTime = preload.value(at: 40) + chapterStrawberries = preload.value(at: 48) + chapterCassette = preload.value(at: 52) + chapterHeart = preload.value(at: 53) + fileTime = preload.value(at: 56) + fileStrawberries = preload.value(at: 64) + fileCassettes = preload.value(at: 68) + fileHearts = preload.value(at: 72) } } @@ -308,8 +239,6 @@ enum ChapterMode : Equatable { A swift representation of the C# AutoSplitterInfo */ class AutoSplitterInfo { - let data: AutoSplitterData - let chapter: Int let mode: ChapterMode let level: String @@ -324,10 +253,8 @@ class AutoSplitterInfo { let fileStrawberries: Int let fileCassettes: Int let fileHearts: Int - + init() { - self.data = AutoSplitterData() - self.chapter = 0 self.mode = .Normal self.level = "" @@ -343,10 +270,8 @@ class AutoSplitterInfo { self.fileCassettes = 0 self.fileHearts = 0 } - - init(from data: AutoSplitterData, in target: MemscanTarget) throws { - self.data = data - + + init(from data: AutoSplitterData) throws { self.chapter = Int(data.chapter) switch data.mode { case -1: @@ -360,7 +285,7 @@ class AutoSplitterInfo { default: self.mode = .Other(value: Int(data.mode)) } - self.level = try AutoSplitterData.parseCSharpString(at: data.levelPointer, target: target) + self.level = try Mono.readString(at: data.level) self.timerActive = data.timerActive != 0 self.chapterStarted = data.chapterStarted != 0 self.chapterComplete = data.chapterComplete != 0 @@ -374,3 +299,16 @@ class AutoSplitterInfo { self.fileHearts = Int(data.fileHearts) } } + +struct ExtendedAutoSplitterData { + var madelineX: Float = 0.0 + var madelineY: Float = 0.0 + + init() {} + + init(from pointer: RmaPointer) throws { + madelineX = try pointer.value(at: 0) + madelineY = try pointer.value(at: 4) + } +} + diff --git a/SwiftSplit/Core/MemoryScanner.swift b/SwiftSplit/Core/MemoryScanner.swift index 714b90e..24feb95 100644 --- a/SwiftSplit/Core/MemoryScanner.swift +++ b/SwiftSplit/Core/MemoryScanner.swift @@ -15,7 +15,7 @@ enum MemscanError : Error { case readNullPointer } -class MemscanSignature { +final class MemscanSignature { var native: memscan_signature /** @@ -75,7 +75,7 @@ class MemscanSignature { } } -class MemscanTarget { +final class MemscanTarget { let native: memscan_target init(pid: pid_t) throws { @@ -100,7 +100,7 @@ class MemscanTarget { } } -class MemscanReadResult { +final class MemscanReadResult { let buffer: UnsafeRawBufferPointer init(with buffer: UnsafeRawBufferPointer) { @@ -139,7 +139,7 @@ class MemscanReadResult { } -class MemscanFilter { +final class MemscanFilter { let native: memscan_filter init(startAddress: vm_address_t, endAddress: vm_address_t) { @@ -150,7 +150,7 @@ class MemscanFilter { } } -class MemscanScanner { +final class MemscanScanner { let native: OpaquePointer private var nativeSignature: UnsafePointer @@ -180,7 +180,7 @@ class MemscanScanner { } } -class MemscanMatch { +final class MemscanMatch { let native: memscan_match var address: vm_address_t { @@ -188,6 +188,18 @@ class MemscanMatch { return native.address } } + + var searchedBytes: Int { + get { + return native.searched_bytes + } + } + + var retriedBytes: Int { + get { + return native.retried_bytes + } + } init(native: memscan_match) { self.native = native diff --git a/SwiftSplit/Core/Mono.swift b/SwiftSplit/Core/Mono.swift new file mode 100644 index 0000000..95b6a95 --- /dev/null +++ b/SwiftSplit/Core/Mono.swift @@ -0,0 +1,32 @@ +// +// MonoMemory.swift +// SwiftSplit +// +// Created by Pierce Corcoran on 7/8/21. +// Copyright © 2021 Pierce Corcoran. All rights reserved. +// + +import Foundation + +final class Mono { + private init() {} + + /** + Parse a C# string object + + Memory layout: + ``` + "NUM:0" + ``` + ``` + header length "N U M : 0 " + a8cd0054c57f0000 0000000000000000 05000000 4e00 5500 4d00 3a00 3000 + 0 8 16 20 + ``` + */ + static func readString(at pointer: RmaPointer) throws -> String { + let length: Int32 = try pointer.value(at: 16) + let stringData = try pointer.raw(at: 20, count: vm_offset_t(length) * 2) + return String(utf16CodeUnits: stringData.buffer.bindMemory(to: unichar.self).baseAddress!, count: Int(length)) + } +} diff --git a/SwiftSplit/Core/RemoteMemoryAccess.swift b/SwiftSplit/Core/RemoteMemoryAccess.swift new file mode 100644 index 0000000..4565255 --- /dev/null +++ b/SwiftSplit/Core/RemoteMemoryAccess.swift @@ -0,0 +1,112 @@ +// +// MonoProcess.swift +// SwiftSplit +// +// Created by Pierce Corcoran on 7/8/21. +// Copyright © 2021 Pierce Corcoran. All rights reserved. +// + +import Foundation + +class RmaProcess { + let target: MemscanTarget + let filter: MemscanFilter + + init(target: MemscanTarget, filter: MemscanFilter) { + self.target = target + self.filter = filter + } + + func findPointer(by signature: MemscanSignature) throws -> RmaPointer? { + let pointers = try findPointers(by: signature, max: 1) + if pointers.count == 0 { + return nil + } + return pointers[0] + } + + func findPointers(by signature: MemscanSignature, max: Int = Int.max) throws -> [RmaPointer] { + var pointers: [RmaPointer] = [] + let scanner = MemscanScanner(target: target, signature: signature, filter: filter) + while let match = try scanner.next() { + pointers.append(pointer(at: match.address)) + if pointers.count >= max { + break + } + } + return pointers + } + + func pointer(at address: vm_address_t) -> RmaPointer { + return RmaPointer(target, at: address) + } +} + +struct RmaPointer { + private let target: MemscanTarget + let address: vm_address_t + + init(_ target: MemscanTarget, at address: vm_address_t) { + self.target = target + self.address = address + } + + func offset(by offset: Int) -> RmaPointer { + return RmaPointer(target, at: address.advanced(by: offset)) + } + + func value(at offset: Int = 0, as type: T.Type = T.self) throws -> T { + let result = try target.read(at: address.advanced(by: offset), count: UInt(MemoryLayout.size)) + return result.buffer.load(as: type) + } + + func value(at offset: Int = 0) throws -> RmaPointer { + return RmaPointer(target, at: try self.value(at: offset)) + } + + func read(bytes: vm_offset_t) throws -> [UInt8] { + let result = try target.read(at: address, count: bytes) + return Array(result.buffer.bindMemory(to: UInt8.self)) + } + + func preload(size: vm_offset_t) throws -> RmaValue { + return RmaValue(target, result: try target.read(at: address, count: size)) + } + + func raw(at offset: Int, count: vm_offset_t) throws -> MemscanReadResult { + return try target.read(at: address.advanced(by: offset), count: count) + } +} + +struct RmaValue { + private let target: MemscanTarget + private let result: MemscanReadResult + + init(_ target: MemscanTarget, result: MemscanReadResult) { + self.target = target + self.result = result + } + + func value(at offset: Int = 0, as type: T.Type = T.self) -> T { + return result.buffer.load(fromByteOffset: offset, as: type) + } + + func value(at offset: Int = 0) -> RmaPointer { + return RmaPointer(target, at: self.value(at: offset)) + } +} + +extension MemscanSignature : ExpressibleByStringLiteral { + typealias StringLiteralType = String + + convenience init(stringLiteral value: String) { + self.init(parsing: value) + } +} + +extension MemscanSignature { + convenience init(address: vm_address_t) { + self.init(from: withUnsafeBytes(of: address.littleEndian, Array.init)) + } +} + diff --git a/SwiftSplit/Core/memory_scanner.c b/SwiftSplit/Core/memory_scanner.c index d4c46d1..622fb2f 100644 --- a/SwiftSplit/Core/memory_scanner.c +++ b/SwiftSplit/Core/memory_scanner.c @@ -125,6 +125,9 @@ struct memscan_scanner_t { The address of the current search. If this is negative, it's addressing into the previous_page_buffer (-1 is the last, -2 the one before that, etc.) */ int scan_index; + + size_t searched_bytes; + size_t retried_bytes; }; void memscan_scanner_free(memscan_scanner *scanner) { @@ -288,18 +291,23 @@ bool memscan_scanner_next(memscan_scanner *scanner, memscan_match *match, memsca // the match failed. Roll back to right after this match attempt started. // If this match failed on the first character has the effect of incrementing if(match_progress > 0) { + scanner->retried_bytes += match_progress; scanner->scan_index -= match_progress; // first roll back to exactly where it started } + scanner->searched_bytes++; scanner->scan_index++; // then step forward by one match_progress = 0; } else { // the match is progressing match_progress++; + scanner->searched_bytes++; scanner->scan_index++; if(match_progress == scanner->signature->length) { *match = (memscan_match){ - .address = scanner->page_address + scanner->scan_index - match_progress + .address = scanner->page_address + scanner->scan_index - match_progress, + .searched_bytes = scanner->searched_bytes, + .retried_bytes = scanner->retried_bytes }; return true; } diff --git a/SwiftSplit/Core/memory_scanner.h b/SwiftSplit/Core/memory_scanner.h index f817237..6168b03 100644 --- a/SwiftSplit/Core/memory_scanner.h +++ b/SwiftSplit/Core/memory_scanner.h @@ -12,6 +12,7 @@ #include #include #include +#include typedef struct memscan_target_t { pid_t pid; @@ -80,6 +81,8 @@ typedef struct memscan_match_t { The address of the match. This points to the start of the signature. */ vm_address_t address; + size_t searched_bytes; + size_t retried_bytes; } memscan_match; /** diff --git a/SwiftSplit/ViewController.swift b/SwiftSplit/ViewController.swift index ace4357..b77ac9c 100644 --- a/SwiftSplit/ViewController.swift +++ b/SwiftSplit/ViewController.swift @@ -186,14 +186,10 @@ class ViewController: NSViewController, RouteBoxDelegate { let info = splitter.autoSplitterInfo if showCelesteData { - celesteDataLabel.stringValue = """ - Object Header: \(String(format: "%08llx", info.data.header)) - Object Location: \(String(format: "%08llx", info.data.location)) - + var dataText = """ Chapter: \(info.chapter) Mode: \(info.mode) Level: - Name Pointer: \(String(format: "%llx", info.data.levelPointer)) Name: \(info.level) Timer Active: \(info.timerActive) Chapter: @@ -209,6 +205,22 @@ class ViewController: NSViewController, RouteBoxDelegate { Cassettes: \(info.fileCassettes) Hearts: \(info.fileHearts) """ + let extended = try? self.splitter?.scanner.readExtended() + if let extended = extended { + var address = "none" + if let p = self.splitter?.scanner.extendedInfo?.address { + address = String(format: "%llx", p) + } + dataText += """ + + Extended: + Address: \(address) + Madeline X: \(extended.madelineX) + Madeline Y: \(extended.madelineY) + """ + + } + celesteDataLabel.stringValue = dataText } else { celesteDataLabel.stringValue = "…" }