Skip to content

Commit c643184

Browse files
committed
v1.1.5: Performance improvements and UI cleanup
- Async file loading with GCD for responsive UI during large binary loads - Removed debug logging for better performance - Java decompiler integration - Improved function name display formatting - UI cleanup: removed redundant function name from toolbar
1 parent 8b97dd6 commit c643184

11 files changed

Lines changed: 3444 additions & 94 deletions

File tree

Sources/Aether/App/AppState.swift

Lines changed: 115 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import SwiftUI
22
import Combine
33

44
func debug(_ message: String) {
5-
fputs(">>> \(message)\n", stderr)
5+
// Debug logging disabled for performance
66
}
77

88
@MainActor
@@ -90,6 +90,7 @@ class AppState: ObservableObject {
9090
private let stringAnalyzer = StringAnalyzer()
9191
private let xrefAnalyzer = XRefAnalyzer()
9292
private let decompiler = Decompiler()
93+
private let javaDecompiler = JavaDecompiler()
9394

9495
// MARK: - File Operations
9596

@@ -139,71 +140,80 @@ class AppState: ObservableObject {
139140
}
140141

141142
func loadFile(url: URL) async {
142-
debug("loadFile called for: \(url.path)")
143143
isLoading = true
144144
loadingProgress = 0
145145
loadingMessage = "Loading file..."
146146
errorMessage = nil
147147

148+
// Capture references for background work
149+
let loader = binaryLoader
150+
let strAnalyzer = stringAnalyzer
151+
148152
do {
149-
// Load binary
150153
loadingMessage = "Parsing binary format..."
151154
loadingProgress = 0.1
152-
debug("Parsing binary...")
153-
154-
let binary = try await binaryLoader.load(from: url)
155-
debug("Binary loaded: \(binary.name)")
156-
debug("Format: \(binary.format), Arch: \(binary.architecture)")
157-
debug("Sections: \(binary.sections.count), Symbols: \(binary.symbols.count)")
158-
159-
// Set the current file first
160-
self.currentFile = binary
161-
debug("currentFile set")
162155

163-
// Get first code section
164-
self.selectedSection = binary.sections.first { $0.containsCode }
165-
debug("Selected section: \(selectedSection?.name ?? "none") (flags: \(String(format: "0x%X", selectedSection?.flags ?? 0)))")
166-
167-
// Extract symbols
168-
loadingMessage = "Processing symbols..."
169-
loadingProgress = 0.5
170-
self.imports = binary.symbols.filter { $0.isImport }
171-
self.exports = binary.symbols.filter { $0.isExport }
172-
self.symbols = binary.symbols
173-
debug("Symbols processed: \(symbols.count)")
174-
175-
// Get functions from symbols
176-
loadingMessage = "Extracting functions..."
177-
loadingProgress = 0.7
178-
self.functions = binary.symbols
179-
.filter { $0.type == .function && $0.address != 0 }
180-
.map { Function(name: $0.name, startAddress: $0.address, endAddress: $0.address + max($0.size, 4)) }
181-
.sorted { $0.startAddress < $1.startAddress }
182-
debug("Functions: \(functions.count)")
183-
184-
// Build lookup caches for O(1) access (keep first occurrence for duplicates)
185-
loadingMessage = "Building lookup caches..."
186-
self.symbolsByAddress = symbols.reduce(into: [:]) { dict, symbol in
187-
if symbol.address != 0 && dict[symbol.address] == nil {
188-
dict[symbol.address] = symbol
189-
}
190-
}
191-
self.symbolsByName = symbols.reduce(into: [:]) { dict, symbol in
192-
if dict[symbol.name] == nil {
193-
dict[symbol.name] = symbol
194-
}
195-
}
196-
self.functionsByAddress = functions.reduce(into: [:]) { dict, func_ in
197-
if dict[func_.startAddress] == nil {
198-
dict[func_.startAddress] = func_
156+
// Use continuation with explicit GCD for guaranteed background execution
157+
let result: (BinaryFile, [Symbol], [Symbol], [Symbol], [Function], [UInt64: Symbol], [String: Symbol], [UInt64: Function], [StringReference]) = try await withCheckedThrowingContinuation { continuation in
158+
DispatchQueue.global(qos: .userInitiated).async {
159+
do {
160+
// Load binary on background thread
161+
let binary = try loader.loadSync(from: url)
162+
163+
// Process symbols
164+
let imports = binary.symbols.filter { $0.isImport }
165+
let exports = binary.symbols.filter { $0.isExport }
166+
let symbols = binary.symbols
167+
168+
// Get functions from symbols
169+
let functions = binary.symbols
170+
.filter { $0.type == .function && $0.address != 0 }
171+
.map { Function(name: $0.name, startAddress: $0.address, endAddress: $0.address + max($0.size, 4)) }
172+
.sorted { $0.startAddress < $1.startAddress }
173+
174+
// Build lookup caches
175+
let symbolsByAddress = symbols.reduce(into: [UInt64: Symbol]()) { dict, symbol in
176+
if symbol.address != 0 && dict[symbol.address] == nil {
177+
dict[symbol.address] = symbol
178+
}
179+
}
180+
let symbolsByName = symbols.reduce(into: [String: Symbol]()) { dict, symbol in
181+
if dict[symbol.name] == nil {
182+
dict[symbol.name] = symbol
183+
}
184+
}
185+
let functionsByAddress = functions.reduce(into: [UInt64: Function]()) { dict, func_ in
186+
if dict[func_.startAddress] == nil {
187+
dict[func_.startAddress] = func_
188+
}
189+
}
190+
191+
// Extract strings
192+
let strings = strAnalyzer.analyze(binary: binary)
193+
194+
continuation.resume(returning: (binary, imports, exports, symbols, functions, symbolsByAddress, symbolsByName, functionsByAddress, strings))
195+
} catch {
196+
continuation.resume(throwing: error)
197+
}
199198
}
200199
}
201200

202-
// Extract strings
203-
loadingMessage = "Extracting strings..."
201+
// Update UI on main thread
202+
loadingMessage = "Finalizing..."
204203
loadingProgress = 0.9
205-
self.strings = stringAnalyzer.analyze(binary: binary)
206-
debug("Strings: \(strings.count)")
204+
205+
let (binary, imports, exports, symbols, functions, symbolsByAddress, symbolsByName, functionsByAddress, strings) = result
206+
207+
self.currentFile = binary
208+
self.selectedSection = binary.sections.first { $0.containsCode }
209+
self.imports = imports
210+
self.exports = exports
211+
self.symbols = symbols
212+
self.functions = functions
213+
self.symbolsByAddress = symbolsByAddress
214+
self.symbolsByName = symbolsByName
215+
self.functionsByAddress = functionsByAddress
216+
self.strings = strings
207217

208218
// Initialize patcher
209219
self.patcher = BinaryPatcher(binary: binary)
@@ -214,7 +224,6 @@ class AppState: ObservableObject {
214224
loadingProgress = 1.0
215225
loadingMessage = "Ready"
216226
isLoading = false
217-
debug("Loading complete! isLoading=\(isLoading)")
218227

219228
} catch {
220229
debug("ERROR: \(error)")
@@ -608,6 +617,12 @@ class AppState: ObservableObject {
608617
guard let function = selectedFunction,
609618
let binary = currentFile else { return }
610619

620+
// Check if this is a Java class file
621+
if let javaClasses = binary.javaClasses, !javaClasses.isEmpty {
622+
decompileJavaMethod(function: function, javaClasses: javaClasses)
623+
return
624+
}
625+
611626
Task {
612627
let instructions = await disassembleFunction(function)
613628
decompilerOutput = decompiler.decompile(
@@ -618,6 +633,52 @@ class AppState: ObservableObject {
618633
}
619634
}
620635

636+
private func decompileJavaMethod(function: Function, javaClasses: [JARLoader.JavaClass]) {
637+
let functionName = function.name
638+
639+
// Find matching class and method - use exact matching
640+
for javaClass in javaClasses {
641+
let className = javaClass.thisClass.replacingOccurrences(of: "/", with: ".")
642+
643+
for method in javaClass.methods {
644+
let methodFullName = "\(className).\(method.name)\(method.descriptor)"
645+
646+
if methodFullName == functionName {
647+
// Found the exact method - decompile just this method
648+
let decompiledMethod = javaDecompiler.decompileMethod(method, in: javaClass)
649+
650+
var output = "// Function at 0x\(String(format: "%X", function.startAddress))\n"
651+
output += "// Size: \(method.code?.code.count ?? 0) bytes (bytecode)\n"
652+
output += "// Class: \(className)\n"
653+
output += "// Method: \(method.name)\(method.descriptor)\n\n"
654+
output += "\(decompiledMethod.signature) {\n"
655+
656+
let bodyLines = decompiledMethod.body.split(separator: "\n", omittingEmptySubsequences: false)
657+
for line in bodyLines {
658+
if !line.isEmpty {
659+
output += " \(line)\n"
660+
} else {
661+
output += "\n"
662+
}
663+
}
664+
output += "}\n"
665+
666+
decompilerOutput = output
667+
return
668+
}
669+
}
670+
}
671+
672+
// Fallback: couldn't find the method
673+
decompilerOutput = "// Could not find Java method for: \(functionName)\n// Available methods in loaded classes:\n"
674+
for javaClass in javaClasses.prefix(5) {
675+
let className = javaClass.thisClass.replacingOccurrences(of: "/", with: ".")
676+
for method in javaClass.methods.prefix(3) {
677+
decompilerOutput += "// \(className).\(method.name)\(method.descriptor)\n"
678+
}
679+
}
680+
}
681+
621682
// MARK: - Patching
622683

623684
func patchBytes(at address: UInt64, newBytes: [UInt8], description: String) {

0 commit comments

Comments
 (0)