mirror of
https://github.com/OpenEmu/OpenEmuKit.git
synced 2025-11-01 11:08:14 +00:00
299 lines
11 KiB
Swift
299 lines
11 KiB
Swift
// Copyright (c) 2021, OpenEmu Team
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
// * Neither the name of the OpenEmu Team nor the
|
|
// names of its contributors may be used to endorse or promote products
|
|
// derived from this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY OpenEmu Team ''AS IS'' AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
// DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY
|
|
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import Foundation
|
|
import OpenEmuBase
|
|
|
|
public class OECorePlugin: OEPlugin {
|
|
|
|
override public class var pluginExtension: String {
|
|
"oecoreplugin"
|
|
}
|
|
|
|
override public class var pluginFolder: String {
|
|
"Cores"
|
|
}
|
|
|
|
@objc public class var allPlugins: [OECorePlugin] {
|
|
// swiftlint:disable:next force_cast
|
|
return plugins() as! [OECorePlugin]
|
|
}
|
|
|
|
required init(bundleAtURL bundleURL: URL, name: String?) throws {
|
|
try super.init(bundleAtURL: bundleURL, name: name)
|
|
|
|
// invalidate global cache
|
|
Self.cachedRequiredFiles = nil
|
|
}
|
|
|
|
public static func corePlugin(bundleAtURL bundleURL: URL) -> OECorePlugin? {
|
|
return try? plugin(bundleAtURL: bundleURL)
|
|
}
|
|
|
|
public static func corePlugin(bundleIdentifier identifier: String) -> OECorePlugin? {
|
|
return allPlugins.first(where: {
|
|
$0.bundleIdentifier.caseInsensitiveCompare(identifier) == .orderedSame
|
|
})
|
|
}
|
|
|
|
public static func corePlugins(forSystemIdentifier identifier: String) -> [OECorePlugin] {
|
|
return allPlugins.filter { $0.systemIdentifiers.contains(identifier) }
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
typealias Controller = OEGameCoreController
|
|
|
|
private var _controller: Controller?
|
|
public var controller: OEGameCoreController! {
|
|
if _controller == nil,
|
|
let principalClass = bundle.principalClass {
|
|
_controller = newPluginController(with: principalClass)
|
|
}
|
|
return _controller
|
|
}
|
|
|
|
private func newPluginController(with bundleClass: AnyClass) -> Controller? {
|
|
guard let bundleClass = bundleClass as? Controller.Type else { return nil }
|
|
return bundleClass.init(bundle: bundle)
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
private static var cachedRequiredFiles: [[String: Any]]?
|
|
public static var requiredFiles: [[String: Any]] {
|
|
if cachedRequiredFiles == nil {
|
|
var files: [[String: Any]] = []
|
|
for plugin in allPlugins {
|
|
files.append(contentsOf: plugin.requiredFiles)
|
|
}
|
|
|
|
cachedRequiredFiles = files
|
|
}
|
|
|
|
return cachedRequiredFiles!
|
|
}
|
|
|
|
public var gameCoreClass: AnyClass? {
|
|
return controller?.gameCoreClass
|
|
}
|
|
|
|
public var bundleIdentifier: String {
|
|
// swiftlint:disable:next force_cast
|
|
return infoDictionary["CFBundleIdentifier"] as! String
|
|
}
|
|
|
|
public var systemIdentifiers: [String] {
|
|
return infoDictionary[OEGameCoreSystemIdentifiersKey] as? [String] ?? []
|
|
}
|
|
|
|
public var coreOptions: [String: [String: Any]] {
|
|
return infoDictionary[OEGameCoreOptionsKey] as? [String: [String: Any]] ?? [:]
|
|
}
|
|
|
|
public var requiredFiles: [[String: Any]] {
|
|
var allRequiredFiles: [[String: Any]] = []
|
|
|
|
for value in coreOptions.values {
|
|
if let object = value[OEGameCoreRequiredFilesKey] as? [[String: Any]] {
|
|
allRequiredFiles.append(contentsOf: object)
|
|
}
|
|
}
|
|
|
|
return allRequiredFiles
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
private var isMarkedDeprecatedInInfoPlist: Bool {
|
|
if infoDictionary[OEGameCoreDeprecatedKey] as? Bool != true {
|
|
return false
|
|
}
|
|
|
|
func isValidVersionString(_ string: String) -> Bool {
|
|
if string.isEmpty { return false }
|
|
let validCharacters: Set<Character> = [".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
|
|
return Set(string).isSubset(of: validCharacters)
|
|
}
|
|
|
|
guard let minMacOSVer = self.infoDictionary[OEGameCoreDeprecatedMinMacOSVersionKey] as? String,
|
|
isValidVersionString(minMacOSVer)
|
|
else { return true }
|
|
|
|
let macOSVerComponents = minMacOSVer.components(separatedBy: ".")
|
|
if macOSVerComponents.count < 2 {
|
|
return true
|
|
}
|
|
let minMacOSVerParsed = OperatingSystemVersion(majorVersion: Int(macOSVerComponents[0])!,
|
|
minorVersion: Int(macOSVerComponents[1])!,
|
|
patchVersion: macOSVerComponents.count > 2 ? Int(macOSVerComponents[2])! : 0)
|
|
if ProcessInfo.processInfo.isOperatingSystemAtLeast(minMacOSVerParsed) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
override public var isDeprecated: Bool {
|
|
if isOutOfSupport {
|
|
return true
|
|
}
|
|
return isMarkedDeprecatedInInfoPlist
|
|
}
|
|
|
|
override public var isOutOfSupport: Bool {
|
|
// plugins deprecated 2017-11-04
|
|
let bundleFileName = bundle.bundleURL.lastPathComponent
|
|
let deprecatedPlugins = [
|
|
"Dolphin-Core.oecoreplugin",
|
|
"NeoPop.oecoreplugin",
|
|
"TwoMbit.oecoreplugin",
|
|
"VisualBoyAdvance.oecoreplugin",
|
|
"Yabause.oecoreplugin",
|
|
]
|
|
if deprecatedPlugins.contains(bundleFileName) {
|
|
return true
|
|
}
|
|
|
|
// beta-era plugins
|
|
if let appcastURL = infoDictionary["SUFeedURL"] as? String,
|
|
appcastURL.contains("openemu.org/update") {
|
|
return true
|
|
}
|
|
|
|
// plugins marked as deprecated in the Info.plist keys
|
|
if isMarkedDeprecatedInInfoPlist,
|
|
let deadline = infoDictionary[OEGameCoreSupportDeadlineKey] as? Date,
|
|
Date().compare(deadline) == .orderedDescending {
|
|
// we are past the support deadline; return true to remove the core
|
|
prepareForRemoval()
|
|
return true
|
|
}
|
|
|
|
// missing value for required key 'CFBundleIdentifier' in Info.plist
|
|
if infoDictionary["CFBundleIdentifier"] as? String == nil {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
private func prepareForRemoval() {
|
|
let replacements = infoDictionary[OEGameCoreSuggestedReplacement] as? [String: String]
|
|
|
|
let defaults = UserDefaults.standard
|
|
for systemIdentifier in systemIdentifiers {
|
|
let prefKey = "defaultCore." + systemIdentifier
|
|
if let currentCore = defaults.string(forKey: prefKey),
|
|
currentCore == bundleIdentifier {
|
|
if let replacement = replacements?[systemIdentifier] {
|
|
defaults.set(replacement, forKey: prefKey)
|
|
} else {
|
|
defaults.removeObject(forKey: prefKey)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public extension OECorePlugin {
|
|
|
|
func requiredFiles(forSystemIdentifier systemIdentifier: String) -> [[String: Any]]? {
|
|
let options = coreOptions[systemIdentifier]
|
|
return options?[OEGameCoreRequiredFilesKey] as? [[String: Any]] ?? nil
|
|
}
|
|
|
|
func requiresFiles(forSystemIdentifier systemIdentifier: String) -> Bool {
|
|
let options = coreOptions[systemIdentifier]
|
|
return options?[OEGameCoreRequiresFilesKey] as? Bool ?? false
|
|
}
|
|
|
|
func supportsCheatCode(forSystemIdentifier systemIdentifier: String) -> Bool {
|
|
let options = coreOptions[systemIdentifier]
|
|
return options?[OEGameCoreSupportsCheatCodeKey] as? Bool ?? false
|
|
}
|
|
|
|
func hasGlitches(forSystemIdentifier systemIdentifier: String) -> Bool {
|
|
let options = coreOptions[systemIdentifier]
|
|
return options?[OEGameCoreHasGlitchesKey] as? Bool ?? false
|
|
}
|
|
|
|
func saveStatesNotSupported(forSystemIdentifier systemIdentifier: String) -> Bool {
|
|
let options = coreOptions[systemIdentifier]
|
|
return options?[OEGameCoreSaveStatesNotSupportedKey] as? Bool ?? false
|
|
}
|
|
|
|
func supportsMultipleDiscs(forSystemIdentifier systemIdentifier: String) -> Bool {
|
|
let options = coreOptions[systemIdentifier]
|
|
return options?[OEGameCoreSupportsMultipleDiscsKey] as? Bool ?? false
|
|
}
|
|
|
|
func supportsRewinding(forSystemIdentifier systemIdentifier: String) -> Bool {
|
|
let options = coreOptions[systemIdentifier]
|
|
return options?[OEGameCoreSupportsRewindingKey] as? Bool ?? false
|
|
}
|
|
|
|
func supportsFileInsertion(forSystemIdentifier systemIdentifier: String) -> Bool {
|
|
let options = coreOptions[systemIdentifier]
|
|
return options?[OEGameCoreSupportsFileInsertionKey] as? Bool ?? false
|
|
}
|
|
|
|
func supportsDisplayModeChange(forSystemIdentifier systemIdentifier: String) -> Bool {
|
|
let options = coreOptions[systemIdentifier]
|
|
return options?[OEGameCoreSupportsDisplayModeChangeKey] as? Bool ?? false
|
|
}
|
|
|
|
func rewindInterval(forSystemIdentifier systemIdentifier: String) -> Int {
|
|
let options = coreOptions[systemIdentifier]
|
|
return options?[OEGameCoreRewindIntervalKey] as? Int ?? 0
|
|
}
|
|
|
|
func rewindBufferSeconds(forSystemIdentifier systemIdentifier: String) -> Int {
|
|
let options = coreOptions[systemIdentifier]
|
|
return options?[OEGameCoreRewindBufferSecondsKey] as? Int ?? 0
|
|
}
|
|
}
|
|
|
|
public extension OECorePlugin.Architecture {
|
|
// swiftlint:disable:next identifier_name
|
|
static let x86_64 = "x86_64"
|
|
static let arm64 = "arm64"
|
|
}
|
|
|
|
public extension OECorePlugin {
|
|
typealias Architecture = String
|
|
|
|
var architectures: [Architecture] {
|
|
var architectures: [Architecture] = []
|
|
let executableArchitectures = bundle.executableArchitectures as? [Int] ?? []
|
|
if executableArchitectures.contains(NSBundleExecutableArchitectureX86_64) {
|
|
architectures.append(.x86_64)
|
|
}
|
|
if #available(macOS 11.0, *),
|
|
executableArchitectures.contains(NSBundleExecutableArchitectureARM64) {
|
|
architectures.append(.arm64)
|
|
}
|
|
return architectures
|
|
}
|
|
}
|