Validates melonDS BIOS files
Compares file sizes and MD5 hashes (when relevant) to ensure BIOS files are correct.
This commit is contained in:
parent
826cf7b0b1
commit
3e0a983048
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
1FA4ABA79AB72914FE414A61 /* libPods-Delta.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC866E433B3BA9AE18ABA1EC /* libPods-Delta.a */; };
|
1FA4ABA79AB72914FE414A61 /* libPods-Delta.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC866E433B3BA9AE18ABA1EC /* libPods-Delta.a */; };
|
||||||
|
BF00BEA625B758AA00C8607D /* SystemBIOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF00BEA525B758AA00C8607D /* SystemBIOS.swift */; };
|
||||||
BF02D5DA1DDEBB3000A5E131 /* openvgdb.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = BF02D5D91DDEBB3000A5E131 /* openvgdb.sqlite */; };
|
BF02D5DA1DDEBB3000A5E131 /* openvgdb.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = BF02D5D91DDEBB3000A5E131 /* openvgdb.sqlite */; };
|
||||||
BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF04E6FE1DB8625C000F35D3 /* ControllerSkinsViewController.swift */; };
|
BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF04E6FE1DB8625C000F35D3 /* ControllerSkinsViewController.swift */; };
|
||||||
BF1020E31F95B05B00313182 /* DeltaToDelta2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF1020E21F95B05B00313182 /* DeltaToDelta2.xcmappingmodel */; };
|
BF1020E31F95B05B00313182 /* DeltaToDelta2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF1020E21F95B05B00313182 /* DeltaToDelta2.xcmappingmodel */; };
|
||||||
@ -157,6 +158,7 @@
|
|||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
22506DA00971C4300AF90A35 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
22506DA00971C4300AF90A35 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
A19FF50F55441BC2B2248241 /* Pods-Delta.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Delta.release.xcconfig"; path = "Pods/Target Support Files/Pods-Delta/Pods-Delta.release.xcconfig"; sourceTree = "<group>"; };
|
A19FF50F55441BC2B2248241 /* Pods-Delta.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Delta.release.xcconfig"; path = "Pods/Target Support Files/Pods-Delta/Pods-Delta.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
BF00BEA525B758AA00C8607D /* SystemBIOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemBIOS.swift; sourceTree = "<group>"; };
|
||||||
BF02D5D91DDEBB3000A5E131 /* openvgdb.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = openvgdb.sqlite; sourceTree = "<group>"; };
|
BF02D5D91DDEBB3000A5E131 /* openvgdb.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = openvgdb.sqlite; sourceTree = "<group>"; };
|
||||||
BF0418131D01E93400E85BCF /* GBADeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GBADeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
BF0418131D01E93400E85BCF /* GBADeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GBADeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
BF04E6FE1DB8625C000F35D3 /* ControllerSkinsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllerSkinsViewController.swift; sourceTree = "<group>"; };
|
BF04E6FE1DB8625C000F35D3 /* ControllerSkinsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllerSkinsViewController.swift; sourceTree = "<group>"; };
|
||||||
@ -718,6 +720,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
BFE9907F24451E15006409A7 /* MelonDSCoreSettingsViewController.swift */,
|
BFE9907F24451E15006409A7 /* MelonDSCoreSettingsViewController.swift */,
|
||||||
|
BF00BEA525B758AA00C8607D /* SystemBIOS.swift */,
|
||||||
);
|
);
|
||||||
path = Cores;
|
path = Cores;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1142,6 +1145,7 @@
|
|||||||
BF6BF3271EB87EB8008E83CD /* PhotoLibraryImportOption.swift in Sources */,
|
BF6BF3271EB87EB8008E83CD /* PhotoLibraryImportOption.swift in Sources */,
|
||||||
BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */,
|
BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */,
|
||||||
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */,
|
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */,
|
||||||
|
BF00BEA625B758AA00C8607D /* SystemBIOS.swift in Sources */,
|
||||||
BF696B801D9B2B02009639E0 /* Theme.swift in Sources */,
|
BF696B801D9B2B02009639E0 /* Theme.swift in Sources */,
|
||||||
BF7AE8081C2E858400B1B5BC /* GridMenuViewController.swift in Sources */,
|
BF7AE8081C2E858400B1B5BC /* GridMenuViewController.swift in Sources */,
|
||||||
BF6BF31A1EB82146008E83CD /* ClipboardImportOption.swift in Sources */,
|
BF6BF31A1EB82146008E83CD /* ClipboardImportOption.swift in Sources */,
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import SafariServices
|
import SafariServices
|
||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
|
import CryptoKit
|
||||||
|
|
||||||
import DeltaCore
|
import DeltaCore
|
||||||
import MelonDSDeltaCore
|
import MelonDSDeltaCore
|
||||||
@ -27,44 +28,77 @@ private extension MelonDSCoreSettingsViewController
|
|||||||
case changeCore
|
case changeCore
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DSBIOS: Int
|
enum BIOSError: LocalizedError
|
||||||
{
|
{
|
||||||
case bios7
|
case unknownSize(URL)
|
||||||
case bios9
|
case incorrectHash(URL, hash: String, expectedHash: String)
|
||||||
case firmware
|
|
||||||
|
|
||||||
var fileURL: URL {
|
@available(iOS 13, *)
|
||||||
|
case incorrectSize(URL, size: Int, validSizes: Set<ClosedRange<Measurement<UnitInformationStorage>>>)
|
||||||
|
|
||||||
|
private static let byteFormatter: ByteCountFormatter = {
|
||||||
|
let formatter = ByteCountFormatter()
|
||||||
|
formatter.includesActualByteCount = true
|
||||||
|
formatter.countStyle = .binary
|
||||||
|
return formatter
|
||||||
|
}()
|
||||||
|
|
||||||
|
var errorDescription: String? {
|
||||||
switch self
|
switch self
|
||||||
{
|
{
|
||||||
case .bios7: return MelonDSEmulatorBridge.shared.bios7URL
|
case .unknownSize(let fileURL):
|
||||||
case .bios9: return MelonDSEmulatorBridge.shared.bios9URL
|
return String(format: NSLocalizedString("%@’s size could not be determined.", comment: ""), fileURL.lastPathComponent)
|
||||||
case .firmware: return MelonDSEmulatorBridge.shared.firmwareURL
|
|
||||||
|
case .incorrectHash(let fileURL, let md5Hash, let expectedHash):
|
||||||
|
return String(format: NSLocalizedString("%@‘s hash does not match the expected hash.\n\nHash:\n%@\n\nExpected:\n%@.", comment: ""), fileURL.lastPathComponent, md5Hash, expectedHash)
|
||||||
|
|
||||||
|
case .incorrectSize(let fileURL, let size, let validSizes):
|
||||||
|
let actualSize = BIOSError.byteFormatter.string(fromByteCount: Int64(size))
|
||||||
|
|
||||||
|
if let range = validSizes.first, validSizes.count == 1
|
||||||
|
{
|
||||||
|
if range.lowerBound == range.upperBound
|
||||||
|
{
|
||||||
|
// Single value
|
||||||
|
let expectedSize = BIOSError.byteFormatter.string(fromByteCount: Int64(range.lowerBound.converted(to: .bytes).value))
|
||||||
|
return String(format: NSLocalizedString("%@ is %@, but expected size is %@.", comment: ""), fileURL.lastPathComponent, actualSize, expectedSize)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Range
|
||||||
|
BIOSError.byteFormatter.includesActualByteCount = false
|
||||||
|
defer { BIOSError.byteFormatter.includesActualByteCount = true }
|
||||||
|
|
||||||
|
let lowerBound = BIOSError.byteFormatter.string(fromByteCount: Int64(range.lowerBound.converted(to: .bytes).value))
|
||||||
|
let upperBound = BIOSError.byteFormatter.string(fromByteCount: Int64(range.upperBound.converted(to: .bytes).value))
|
||||||
|
return String(format: NSLocalizedString("%@ is %@, but expected size is between %@ and %@.", comment: ""), fileURL.lastPathComponent, actualSize, lowerBound, upperBound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var description = String(format: NSLocalizedString("%@ is %@, but expected sizes are:", comment: ""), fileURL.lastPathComponent, actualSize) + "\n"
|
||||||
|
|
||||||
|
let sortedRanges = validSizes.sorted(by: { $0.lowerBound < $1.lowerBound })
|
||||||
|
for range in sortedRanges
|
||||||
|
{
|
||||||
|
// Assume BIOS with multiple valid file sizes don't use (>1 count) ranges.
|
||||||
|
description += "\n" + BIOSError.byteFormatter.string(fromByteCount: Int64(range.lowerBound.converted(to: .bytes).value))
|
||||||
|
}
|
||||||
|
|
||||||
|
return description
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
enum DSiBIOS: Int
|
|
||||||
{
|
|
||||||
case bios7
|
|
||||||
case bios9
|
|
||||||
case firmware
|
|
||||||
case nand
|
|
||||||
|
|
||||||
var fileURL: URL {
|
var recoverySuggestion: String? {
|
||||||
switch self
|
return NSLocalizedString("Please choose a different BIOS file.", comment: "")
|
||||||
{
|
|
||||||
case .bios7: return MelonDSEmulatorBridge.shared.dsiBIOS7URL
|
|
||||||
case .bios9: return MelonDSEmulatorBridge.shared.dsiBIOS9URL
|
|
||||||
case .firmware: return MelonDSEmulatorBridge.shared.dsiFirmwareURL
|
|
||||||
case .nand: return MelonDSEmulatorBridge.shared.dsiNANDURL
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MelonDSCoreSettingsViewController: UITableViewController
|
class MelonDSCoreSettingsViewController: UITableViewController
|
||||||
{
|
{
|
||||||
private var importDestinationURL: URL?
|
private var importingBIOS: SystemBIOS?
|
||||||
|
|
||||||
override func viewDidLoad()
|
override func viewDidLoad()
|
||||||
{
|
{
|
||||||
@ -121,9 +155,9 @@ private extension MelonDSCoreSettingsViewController
|
|||||||
self.present(safariViewController, animated: true, completion: nil)
|
self.present(safariViewController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func locateBIOS(for destinationURL: URL)
|
func locate<BIOS: SystemBIOS>(_ bios: BIOS)
|
||||||
{
|
{
|
||||||
self.importDestinationURL = destinationURL
|
self.importingBIOS = bios
|
||||||
|
|
||||||
var supportedTypes = [kUTTypeItem as String, kUTTypeContent as String, "com.apple.macbinary-archive" /* System UTI for .bin */]
|
var supportedTypes = [kUTTypeItem as String, kUTTypeContent as String, "com.apple.macbinary-archive" /* System UTI for .bin */]
|
||||||
|
|
||||||
@ -220,7 +254,7 @@ extension MelonDSCoreSettingsViewController
|
|||||||
cell.contentView.isHidden = (item == nil)
|
cell.contentView.isHidden = (item == nil)
|
||||||
|
|
||||||
case .dsBIOS:
|
case .dsBIOS:
|
||||||
let bios = DSBIOS(rawValue: indexPath.row)!
|
let bios = DSBIOS.allCases[indexPath.row]
|
||||||
|
|
||||||
if FileManager.default.fileExists(atPath: bios.fileURL.path)
|
if FileManager.default.fileExists(atPath: bios.fileURL.path)
|
||||||
{
|
{
|
||||||
@ -238,7 +272,7 @@ extension MelonDSCoreSettingsViewController
|
|||||||
cell.selectionStyle = .default
|
cell.selectionStyle = .default
|
||||||
|
|
||||||
case .dsiBIOS:
|
case .dsiBIOS:
|
||||||
let bios = DSiBIOS(rawValue: indexPath.row)!
|
let bios = DSiBIOS.allCases[indexPath.row]
|
||||||
|
|
||||||
if FileManager.default.fileExists(atPath: bios.fileURL.path)
|
if FileManager.default.fileExists(atPath: bios.fileURL.path)
|
||||||
{
|
{
|
||||||
@ -288,12 +322,12 @@ extension MelonDSCoreSettingsViewController
|
|||||||
self.openMetadataURL(for: key)
|
self.openMetadataURL(for: key)
|
||||||
|
|
||||||
case .dsBIOS:
|
case .dsBIOS:
|
||||||
let bios = DSBIOS(rawValue: indexPath.row)!
|
let bios = DSBIOS.allCases[indexPath.row]
|
||||||
self.locateBIOS(for: bios.fileURL)
|
self.locate(bios)
|
||||||
|
|
||||||
case .dsiBIOS:
|
case .dsiBIOS:
|
||||||
let bios = DSiBIOS(rawValue: indexPath.row)!
|
let bios = DSiBIOS.allCases[indexPath.row]
|
||||||
self.locateBIOS(for: bios.fileURL)
|
self.locate(bios)
|
||||||
|
|
||||||
case .changeCore:
|
case .changeCore:
|
||||||
self.changeCore()
|
self.changeCore()
|
||||||
@ -374,28 +408,58 @@ extension MelonDSCoreSettingsViewController: UIDocumentPickerDelegate
|
|||||||
{
|
{
|
||||||
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController)
|
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController)
|
||||||
{
|
{
|
||||||
self.importDestinationURL = nil
|
self.importingBIOS = nil
|
||||||
self.tableView.reloadData() // Reloading index path causes cell to disappear...
|
self.tableView.reloadData() // Reloading index path causes cell to disappear...
|
||||||
}
|
}
|
||||||
|
|
||||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL])
|
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL])
|
||||||
{
|
{
|
||||||
defer {
|
defer {
|
||||||
self.importDestinationURL = nil
|
self.importingBIOS = nil
|
||||||
self.tableView.reloadData() // Reloading index path causes cell to disappear...
|
self.tableView.reloadData() // Reloading index path causes cell to disappear...
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let fileURL = urls.first, let destinationURL = self.importDestinationURL else { return }
|
guard let fileURL = urls.first, let bios = self.importingBIOS else { return }
|
||||||
|
|
||||||
|
defer { try? FileManager.default.removeItem(at: fileURL) }
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
try FileManager.default.copyItem(at: fileURL, to: destinationURL, shouldReplace: true)
|
if #available(iOS 13.0, *)
|
||||||
|
{
|
||||||
|
// Validate file size first (since that's easiest for users to understand).
|
||||||
|
|
||||||
|
let attributes = try FileManager.default.attributesOfItem(atPath: fileURL.path)
|
||||||
|
guard let fileSize = attributes[.size] as? Int else { throw BIOSError.unknownSize(fileURL) }
|
||||||
|
|
||||||
|
let measurement = Measurement<UnitInformationStorage>(value: Double(fileSize), unit: .bytes)
|
||||||
|
guard bios.validFileSizes.contains(where: { $0.contains(measurement) }) else { throw BIOSError.incorrectSize(fileURL, size: fileSize, validSizes: bios.validFileSizes) }
|
||||||
|
|
||||||
|
if let expectedMD5Hash = bios.expectedMD5Hash
|
||||||
|
{
|
||||||
|
// If there's an expected hash, make sure it matches.
|
||||||
|
|
||||||
|
let data = try Data(contentsOf: fileURL)
|
||||||
|
|
||||||
|
let md5Hash = Insecure.MD5.hash(data: data)
|
||||||
|
let hashString = md5Hash.compactMap { String(format: "%02x", $0) }.joined()
|
||||||
|
guard hashString == expectedMD5Hash else { throw BIOSError.incorrectHash(fileURL, hash: hashString, expectedHash: expectedMD5Hash) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try FileManager.default.copyItem(at: fileURL, to: bios.fileURL, shouldReplace: true)
|
||||||
}
|
}
|
||||||
catch
|
catch let error as NSError
|
||||||
{
|
{
|
||||||
let title = String(format: NSLocalizedString("Could not import %@.", comment: ""), fileURL.lastPathComponent)
|
let title = String(format: NSLocalizedString("Could not import %@.", comment: ""), bios.filename)
|
||||||
|
|
||||||
let alertController = UIAlertController(title: title, message: error.localizedDescription, preferredStyle: .alert)
|
var message = error.localizedDescription
|
||||||
|
if let recoverySuggestion = error.localizedRecoverySuggestion
|
||||||
|
{
|
||||||
|
message += "\n\n" + recoverySuggestion
|
||||||
|
}
|
||||||
|
|
||||||
|
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||||
alertController.addAction(.ok)
|
alertController.addAction(.ok)
|
||||||
self.present(alertController, animated: true, completion: nil)
|
self.present(alertController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|||||||
123
Delta/Settings/Cores/SystemBIOS.swift
Normal file
123
Delta/Settings/Cores/SystemBIOS.swift
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
//
|
||||||
|
// SystemBIOS.swift
|
||||||
|
// Delta
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 1/19/21.
|
||||||
|
// Copyright © 2021 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
import MelonDSDeltaCore
|
||||||
|
|
||||||
|
protocol SystemBIOS
|
||||||
|
{
|
||||||
|
var fileURL: URL { get }
|
||||||
|
var filename: String { get }
|
||||||
|
|
||||||
|
var expectedMD5Hash: String? { get }
|
||||||
|
|
||||||
|
// RangeSet would be preferable, but it's not in Swift stdlib yet.
|
||||||
|
@available(iOS 13, *)
|
||||||
|
var validFileSizes: Set<ClosedRange<Measurement<UnitInformationStorage>>> { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SystemBIOS
|
||||||
|
{
|
||||||
|
var filename: String {
|
||||||
|
return self.fileURL.lastPathComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DSBIOS: SystemBIOS, CaseIterable
|
||||||
|
{
|
||||||
|
case bios7
|
||||||
|
case bios9
|
||||||
|
case firmware
|
||||||
|
|
||||||
|
var fileURL: URL {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .bios7: return MelonDSEmulatorBridge.shared.bios7URL
|
||||||
|
case .bios9: return MelonDSEmulatorBridge.shared.bios9URL
|
||||||
|
case .firmware: return MelonDSEmulatorBridge.shared.firmwareURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedMD5Hash: String? {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .bios7: return "df692a80a5b1bc90728bc3dfc76cd948"
|
||||||
|
case .bios9: return "a392174eb3e572fed6447e956bde4b25"
|
||||||
|
case .firmware: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 13, *)
|
||||||
|
var validFileSizes: Set<ClosedRange<Measurement<UnitInformationStorage>>> {
|
||||||
|
// From http://melonds.kuribo64.net/faq.php
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .bios7:
|
||||||
|
// 16KB
|
||||||
|
return Set([16].map { Measurement(value: $0, unit: .kibibytes) }.map { $0...$0 })
|
||||||
|
case .bios9:
|
||||||
|
// 4KB
|
||||||
|
return Set([4].map { Measurement(value: $0, unit: .kibibytes) }.map { $0...$0 })
|
||||||
|
case .firmware:
|
||||||
|
// 128KB, 256KB, or 512KB
|
||||||
|
return Set([128, 256, 512].map { Measurement(value: $0, unit: .kibibytes) }.map { $0...$0 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DSiBIOS: SystemBIOS, CaseIterable
|
||||||
|
{
|
||||||
|
case bios7
|
||||||
|
case bios9
|
||||||
|
case firmware
|
||||||
|
case nand
|
||||||
|
|
||||||
|
var fileURL: URL {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .bios7: return MelonDSEmulatorBridge.shared.dsiBIOS7URL
|
||||||
|
case .bios9: return MelonDSEmulatorBridge.shared.dsiBIOS9URL
|
||||||
|
case .firmware: return MelonDSEmulatorBridge.shared.dsiFirmwareURL
|
||||||
|
case .nand: return MelonDSEmulatorBridge.shared.dsiNANDURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedMD5Hash: String? {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .bios7: return "559dae4ea78eb9d67702c56c1d791e81"
|
||||||
|
case .bios9: return "87b665fce118f76251271c3732532777"
|
||||||
|
case .firmware: return nil
|
||||||
|
case .nand: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 13, *)
|
||||||
|
var validFileSizes: Set<ClosedRange<Measurement<UnitInformationStorage>>> {
|
||||||
|
// From http://melonds.kuribo64.net/faq.php
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .bios7:
|
||||||
|
// 64KB
|
||||||
|
return Set([64].map { Measurement(value: $0, unit: .kibibytes) }.map { $0...$0 })
|
||||||
|
|
||||||
|
case .bios9:
|
||||||
|
// 64KB
|
||||||
|
return Set([64].map { Measurement(value: $0, unit: .kibibytes) }.map { $0...$0 })
|
||||||
|
|
||||||
|
case .firmware:
|
||||||
|
// 128KB
|
||||||
|
return Set([128].map { Measurement(value: $0, unit: .kibibytes) }.map { $0...$0 })
|
||||||
|
|
||||||
|
case .nand:
|
||||||
|
// 200MB - 300MB
|
||||||
|
return Set([200...300].map { Measurement(value: $0.lowerBound, unit: .mebibytes) ... Measurement(value: $0.upperBound, unit: .mebibytes) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user