Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
folium-app
GitHub Repository: folium-app/Folium
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() {}
}