Path: blob/a-new-beginning/CombinedCores/CombinedCores.swift
2 views
//
// CombinedCores.swift
// CombinedCores
//
// Created by Jarrod Norwell on 4/3/2026.
//
import Grape
import Guava
import Kiwi
import Mandarine
public actor CombinedCores {
public actor GrapeCore {
public enum GrapeButton : UInt32 {
case a = 0x00000001,
b = 0x00000002,
x = 0x00000400,
y = 0x00000800
case right = 0x00000010,
left = 0x00000020,
up = 0x00000040,
down = 0x00000080
case select = 0x00000004,
start = 0x00000008
case l = 0x00000200,
r = 0x00000100
}
let grape: Grape = Grape()
public init() {}
public func insert(cartridge: URL) async {
await grape.insert(cartridge: cartridge)
}
public func icon(cartridge: URL) async -> IconObject {
await grape.icon(cartridge: cartridge)
}
public func pause() async {
await grape.pause()
}
public func start() async {
await grape.start()
}
public func stop() async {
await grape.stop()
}
public func unpause() async {
await grape.unpause()
}
public var paused: Bool {
get async {
await grape.paused
}
}
public var running: Bool {
get async {
await grape.running
}
}
public func touchBegan(point: CGPoint) async {
await grape.touchBegan(point: point)
}
public func touchEnded() async {
await grape.touchEnded()
}
public func touchMoved(point: CGPoint) async {
await grape.touchMoved(point: point)
}
public func press(button: GrapeButton) async {
await grape.press(button: button.rawValue)
}
public func release(button: GrapeButton) async {
await grape.release(button: button.rawValue)
}
public func load(state: URL) async {
await grape.load(state: state)
}
public func save(state: URL) async {
await grape.save(state: state)
}
public func audioCallback(output: @escaping @Sendable (UnsafeMutablePointer<Int16>, Int) -> Void) async {
await grape.audioCallback { buffer, samples in
output(buffer, samples)
}
}
public func videoCallback(output: @escaping @Sendable (UnsafeMutablePointer<UInt32>, UnsafeMutablePointer<UInt32>) -> Void) async {
await grape.videoCallback { primaryBuffer, secondaryBuffer in
output(primaryBuffer, secondaryBuffer)
}
}
}
public actor GuavaCore {
public enum GuavaButton : UInt32 {
case a = 0x00000000,
b = 0x00000001
case right = 0x00000007,
left = 0x00000006,
up = 0x00000004,
down = 0x00000005
case select = 0x00000002,
start = 0x00000003
}
let guava: Guava = Guava()
public init() {}
public func insert(cartridge: URL) async throws {
try await guava.insert(cartridge: cartridge)
}
public func pause() async {
await guava.pause()
}
public func start() async {
await guava.start()
}
public func step() async {
await guava.step()
}
public func stop() async {
await guava.stop()
}
public func unpause() async {
await guava.unpause()
}
public var paused: Bool {
get async {
await guava.paused
}
}
public var running: Bool {
get async {
await guava.running
}
}
public func press(button: GuavaButton) async {
await guava.press(button: button.rawValue)
}
public func release(button: GuavaButton) async {
await guava.release(button: button.rawValue)
}
public func load(state: URL) async {
await guava.load(state: state)
}
public func save(state: URL) async {
await guava.save(state: state)
}
public var state: [UInt8] {
get async {
await guava.state
}
}
public func setState(with buffer: [UInt8]) async throws {
try await guava.setState(for: buffer)
}
public func audioCallback(output: @escaping @Sendable ([Float]) -> Void) async {
await guava.audioCallback { buffer in
output(buffer)
}
}
public func videoCallback(output: @escaping @Sendable ([UInt8], Int, Int) -> Void) async {
await guava.videoCallback { buffer, width, height in
output(buffer, width, height)
}
}
}
public actor KiwiCore {
public enum KiwiButton : UInt32 {
case a = 0x00000001,
b = 0x00000010
case right = 0x00010000,
left = 0x00100000,
up = 0x01000000,
down = 0x10000000
case select = 0x00000100,
start = 0x00001000
}
let kiwi: Kiwi = Kiwi()
public init() {}
public func insert(cartridge: URL) async {
await kiwi.insert(cartridge: cartridge)
}
public func pause() async {
await kiwi.pause()
}
public func start() async {
await kiwi.start()
}
public func stop() async {
await kiwi.stop()
}
public func unpause() async {
await kiwi.unpause()
}
public var paused: Bool {
get async {
await kiwi.paused
}
}
public var running: Bool {
get async {
await kiwi.running
}
}
public func press(button: KiwiButton) async {
await kiwi.press(button: button.rawValue)
}
public func release(button: KiwiButton) async {
await kiwi.release(button: button.rawValue)
}
public func load(state: URL) async {
await kiwi.load(state: state)
}
public func save(state: URL) async {
await kiwi.save(state: state)
}
public func audioCallback(output: @escaping @Sendable (UnsafeMutablePointer<UInt32>, Int) -> Void) async {
await kiwi.audioCallback { buffer, samples in
output(buffer, samples)
}
}
public func videoCallback(output: @escaping @Sendable (UnsafeMutablePointer<UInt32>, Int, Int) -> Void) async {
await kiwi.videoCallback { buffer, width, height in
output(buffer, width, height)
}
}
public func title(cartridge: URL) async -> String {
await kiwi.title(cartridge: cartridge)
}
}
public actor MandarineCore {
public enum MandarineButton : UInt32 {
case circle = 0x00000001,
cross = 0x00000002,
triangle = 0x00000003,
square = 0x00000004
case right = 0x00000005,
left = 0x00000006,
up = 0x00000007,
down = 0x00000008
case select = 0x00000009,
start = 0x0000000A
case l1 = 0x0000000B,
r1 = 0x0000000C
case l2 = 0x0000000D,
r2 = 0x0000000E
var string: String {
switch self {
case .circle:
"circle"
case .cross:
"cross"
case .triangle:
"triangle"
case .square:
"square"
case .right:
"dpad_right"
case .left:
"dpad_left"
case .up:
"dpad_up"
case .down:
"dpad_down"
case .select:
"select"
case .start:
"start"
case .l1:
"l1"
case .r1:
"r1"
case .l2:
"l2"
case .r2:
"r2"
}
}
}
let mandarine: Mandarine = Mandarine()
public init() {}
public func insert(cartridge: URL) async {
await mandarine.insert(cartridge: cartridge)
}
public func icon(cartridge: URL) async -> UIImage? {
func files(from url: URL) -> [String] {
if let contents: String = try? String(contentsOf: url, encoding: .utf8) {
let lines: [Substring] = contents.split(separator: "\n")
var files: [String] = []
lines.forEach { line in
let trimmed: String = line.trimmingCharacters(in: .whitespaces)
if trimmed.uppercased().starts(with: "FILE") {
let components: [String.SubSequence] = trimmed.split(separator: "\"", omittingEmptySubsequences: false)
if components.count >= 2 {
let path: String = String(components[1])
files.append(path)
}
}
}
return files
} else {
return []
}
}
guard let string: String = files(from: cartridge).first else {
return nil
}
let id: String = await mandarine.identifier(cartridge: cartridge.deletingLastPathComponent().appending(component: string))
let link: String = "https://raw.githubusercontent.com/xlenore/psx-covers/refs/heads/main/covers/default/\(id).jpg"
if let url: URL = URL(string: link) {
if let (data, _) = try? await URLSession.shared.data(from: url) {
return UIImage(data: data)
} else {
return nil
}
} else {
return nil
}
}
public func pause() async {
await mandarine.pause()
}
public func start() async {
await mandarine.start()
}
public func stop() async {
await mandarine.stop()
}
public func unpause() async {
await mandarine.unpause()
}
public var paused: Bool {
get async {
await mandarine.paused
}
}
public var running: Bool {
get async {
await mandarine.running
}
}
public func press(button: MandarineButton) async {
await mandarine.press(button: button.string)
}
public func release(button: MandarineButton) async {
await mandarine.release(button: button.string)
}
public func load(state: URL) async {
await mandarine.load(state: state)
}
public func save(state: URL) async {
await mandarine.save(state: state)
}
public func audioCallback(output: @escaping @Sendable (UnsafeMutablePointer<UInt16>, Int) -> Void) async {
await mandarine.audioCallback { buffer, samples in
output(buffer, samples)
}
}
public func videoCallback(output: @escaping @Sendable (UnsafeMutableRawPointer, Int, Int, Int, Int) -> Void) async {
await mandarine.videoCallback { buffer, x, y, width, height in
output(buffer, x, y, width, height)
}
}
public func secondaryVideoCallback(output: @escaping @Sendable (UnsafeMutablePointer<UInt16>, Int, Int, Int, Int) -> Void) async {
await mandarine.secondaryVideoCallback { buffer, x, y, width, height in
output(buffer, x, y, width, height)
}
}
}
public actor MangoCore {
public init() {}
public func icon(cartridge: URL) async -> UIImage? {
func getSnesRegion(from url: URL) -> String? {
let loROMOffset: UInt64 = 0xFFD9
let hiROMOffset: UInt64 = 0x7FD9
let fileURL = URL(fileURLWithPath: url.path)
guard let handle = try? FileHandle(forReadingFrom: fileURL) else {
return nil
}
defer {
try? handle.close()
}
// Get file size
guard let fileSize = try? handle.seekToEnd() else {
return nil
}
try? handle.seek(toOffset: 0)
let offset: UInt64
if fileSize > loROMOffset {
offset = loROMOffset
} else if fileSize > hiROMOffset {
offset = hiROMOffset
} else {
return nil
}
// Read region byte
try? handle.seek(toOffset: offset)
guard let data = try? handle.read(upToCount: 1) else {
return nil
}
guard let byte = data.first else {
return nil
}
switch byte {
case 0x00: return "Japan"
case 0x01: return "USA"
case 0x02: return "Europe"
case 0x03: return "Sweden"
case 0x04: return "Finland"
case 0x05: return "Denmark"
case 0x06: return "France"
case 0x07: return "Netherlands"
case 0x08: return "Spain"
case 0x09: return "Germany"
case 0x0A: return "Italy"
case 0x0B: return "Hong Kong"
case 0x0C: return "Indonesia"
case 0x0D: return "South Korea"
default: return "USA"
}
}
guard let region = getSnesRegion(from: cartridge) else {
return nil
}
let link: String = "https://raw.githubusercontent.com/libretro/libretro-thumbnails/refs/heads/master/Nintendo - Super Nintendo Entertainment System/Named_Boxarts/\(cartridge.deletingPathExtension().lastPathComponent) (\(region)).png"
if let url: URL = URL(string: link) {
if let (data, _) = try? await URLSession.shared.data(from: url) {
return UIImage(data: data)
} else {
return nil
}
} else {
return nil
}
}
}
public actor TomatoCore {
public init() {}
public func icon(cartridge: URL) async -> UIImage? {
let title: String = cartridge.deletingPathExtension().lastPathComponent
func code(from url: URL) -> String? {
guard let file: FileHandle = try? FileHandle(forReadingFrom: url) else {
return nil
}
try? file.seek(toOffset: 0x0AC)
guard let data = try? file.read(upToCount: 0x04) else {
return title
}
try? file.close()
guard let string: String = .init(data: data, encoding: .ascii),
let destination: Substring = string.trimmingCharacters(in: .controlCharacters).split(separator: "").last else {
return title
}
return String(destination).uppercased()
}
guard let id: String = code(from: cartridge) else {
return nil
}
let link: String = "https://raw.githubusercontent.com/libretro/libretro-thumbnails/refs/heads/master/Nintendo - Game Boy Advance/Named_Boxarts/\(title) (\(id)).png"
if let url: URL = URL(string: link) {
if let (data, _) = try? await URLSession.shared.data(from: url) {
return UIImage(data: data)
} else {
return nil
}
} else {
return nil
}
}
}
public let grapeCore: GrapeCore = GrapeCore()
public let guavaCore: GuavaCore = GuavaCore()
public let kiwiCore: KiwiCore = KiwiCore()
public let mandarineCore: MandarineCore = MandarineCore()
public let mangoCore: MangoCore = MangoCore()
public let tomatoCore: TomatoCore = TomatoCore()
public init() {}
}