mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-24 12:30:17 +00:00
Puts enough in place for a GUI-led installation process.
... and provides a lot of the Objective-C wiring necessary to expose that to Swift.
This commit is contained in:
parent
b0f551c307
commit
5acd97c860
@ -13,6 +13,10 @@
|
||||
|
||||
using namespace ROM;
|
||||
|
||||
namespace {
|
||||
constexpr Name MaxName = Name::SpectrumPlus3;
|
||||
}
|
||||
|
||||
Request::Request(Name name, bool optional) {
|
||||
node.name = name;
|
||||
node.is_optional = optional;
|
||||
@ -173,8 +177,16 @@ void Request::Node::visit(
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Description> ROM::all_descriptions() {
|
||||
std::vector<Description> result;
|
||||
for(int name = 1; name <= MaxName; name++) {
|
||||
result.push_back(Description(ROM::Name(name)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<Description> Description::from_crc(uint32_t crc32) {
|
||||
for(int name = 1; name <= SpectrumPlus3; name++) {
|
||||
for(int name = 1; name <= MaxName; name++) {
|
||||
const Description candidate = Description(ROM::Name(name));
|
||||
|
||||
const auto found_crc = std::find(candidate.crc32s.begin(), candidate.crc32s.end(), crc32);
|
||||
@ -232,8 +244,8 @@ Description::Description(Name name) {
|
||||
case Name::Macintosh128k: *this = Description(name, "Macintosh", "the Macintosh 128k ROM", "mac128k.rom", 64*1024, 0x6d0c8a28u); break;
|
||||
case Name::Macintosh512k: *this = Description(name, "Macintosh", "the Macintosh 512k ROM", "mac512k.rom", 64*1024, 0xcf759e0d); break;
|
||||
case Name::MacintoshPlus: {
|
||||
const std::initializer_list<uint32_t> crc32s = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e };
|
||||
*this = Description(name, "Macintosh", "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s);
|
||||
const std::initializer_list<uint32_t> crcs = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e };
|
||||
*this = Description(name, "Macintosh", "the Macintosh Plus ROM", "macplus.rom", 128*1024, crcs);
|
||||
} break;
|
||||
|
||||
case Name::AtariSTTOS100: *this = Description(name, "AtariST", "the UK TOS 1.00 ROM", "tos100.img", 192*1024, 0x1a586c64u); break;
|
||||
@ -250,8 +262,8 @@ Description::Description(Name name) {
|
||||
case Name::Spectrum128k: *this = Description(name, "ZXSpectrum", "the 128kb ROM", "128.rom", 32 * 1024, 0x2cbe8995u); break;
|
||||
case Name::SpecrumPlus2: *this = Description(name, "ZXSpectrum", "the +2 ROM", "plus2.rom", 32 * 1024, 0xe7a517dcu); break;
|
||||
case Name::SpectrumPlus3: {
|
||||
const std::initializer_list<uint32_t> crc32s = { 0x96e3c17a, 0xbe0d9ec4 };
|
||||
*this = Description(name, "ZXSpectrum", "the +2a/+3 ROM", "plus3.rom", 64 * 1024, crc32s);
|
||||
const std::initializer_list<uint32_t> crcs = { 0x96e3c17a, 0xbe0d9ec4 };
|
||||
*this = Description(name, "ZXSpectrum", "the +2a/+3 ROM", "plus3.rom", 64 * 1024, crcs);
|
||||
} break;
|
||||
|
||||
case Name::AcornBASICII: *this = Description(name, "Electron", "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781u); break;
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -135,7 +136,7 @@ struct Description {
|
||||
/// CRC32s for all known acceptable copies of this ROM; intended to allow a host platform
|
||||
/// to test user-provided ROMs of unknown provenance. **Not** intended to be used
|
||||
/// to exclude ROMs where the user's intent is otherwise clear.
|
||||
std::vector<uint32_t> crc32s;
|
||||
std::set<uint32_t> crc32s;
|
||||
|
||||
/// Constructs the @c Description that correlates to @c name.
|
||||
Description(Name name);
|
||||
@ -145,14 +146,19 @@ struct Description {
|
||||
|
||||
private:
|
||||
template <typename FileNameT, typename CRC32T> Description(
|
||||
Name name, std::string machine_name, std::string descriptive_name, FileNameT file_names, size_t size, CRC32T crc32s = CRC32T(0)
|
||||
Name name, std::string machine_name, std::string descriptive_name, FileNameT file_names, size_t size, CRC32T crc32s = uint32_t(0)
|
||||
) : name{name}, machine_name{machine_name}, descriptive_name{descriptive_name}, file_names{file_names}, size{size}, crc32s{crc32s} {
|
||||
if(this->crc32s.size() == 1 && !this->crc32s[0]) {
|
||||
// Slightly lazy: deal with the case where the constructor wasn't provided with any
|
||||
// CRCs by spotting that the set has exactly one member, which has value 0. The alternative
|
||||
// would be to provide a partial specialisation that never put anything into the set.
|
||||
if(this->crc32s.size() == 1 && !*this->crc32s.begin()) {
|
||||
this->crc32s.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<Description> all_descriptions();
|
||||
|
||||
struct Request {
|
||||
Request(Name name, bool optional = false);
|
||||
Request() {}
|
||||
|
@ -137,22 +137,18 @@ class MachineDocument:
|
||||
volumeSlider.floatValue = userDefaultsVolume()
|
||||
}
|
||||
|
||||
private var missingROMs: [CSMissingROM] = []
|
||||
private var missingROMs: String = ""
|
||||
func configureAs(_ analysis: CSStaticAnalyser) {
|
||||
self.machineDescription = analysis
|
||||
|
||||
actionLock.lock()
|
||||
drawLock.lock()
|
||||
|
||||
let missingROMs = NSMutableArray()
|
||||
if let machine = CSMachine(analyser: analysis, missingROMs: missingROMs) {
|
||||
self.machine = machine
|
||||
machine.setVolume(userDefaultsVolume())
|
||||
setupMachineOutput()
|
||||
} else {
|
||||
// Store the selected machine and list of missing ROMs, and
|
||||
// show the missing ROMs dialogue.
|
||||
self.missingROMs = missingROMs.map({$0 as! CSMissingROM})
|
||||
requestRoms()
|
||||
}
|
||||
|
||||
@ -437,30 +433,7 @@ class MachineDocument:
|
||||
}
|
||||
|
||||
func populateMissingRomList() {
|
||||
// Fill in the missing details; first build a list of all the individual
|
||||
// line items.
|
||||
var requestLines: [String] = []
|
||||
for missingROM in self.missingROMs {
|
||||
if let descriptiveName = missingROM.descriptiveName {
|
||||
requestLines.append("• " + descriptiveName)
|
||||
} else {
|
||||
requestLines.append("• " + missingROM.fileName)
|
||||
}
|
||||
}
|
||||
|
||||
// Suffix everything up to the penultimate line with a semicolon;
|
||||
// the penultimate line with a semicolon and a conjunctive; the final
|
||||
// line with a full stop.
|
||||
for x in 0 ..< requestLines.count {
|
||||
if x < requestLines.count - 2 {
|
||||
requestLines[x].append(";")
|
||||
} else if x < requestLines.count - 1 {
|
||||
requestLines[x].append("; and")
|
||||
} else {
|
||||
requestLines[x].append(".")
|
||||
}
|
||||
}
|
||||
romRequesterText!.stringValue = self.romRequestBaseText + requestLines.joined(separator: "\n")
|
||||
romRequesterText!.stringValue = self.romRequestBaseText + self.missingROMs
|
||||
}
|
||||
|
||||
func romReceiverView(_ view: CSROMReceiverView, didReceiveFileAt URL: URL) {
|
||||
@ -474,7 +447,7 @@ class MachineDocument:
|
||||
// Try to match by size first, CRC second. Accept that some ROMs may have
|
||||
// some additional appended data. Arbitrarily allow them to be up to 10kb
|
||||
// too large.
|
||||
var index = 0
|
||||
/* var index = 0
|
||||
for missingROM in self.missingROMs {
|
||||
if fileData.count >= missingROM.size && fileData.count < missingROM.size + 10*1024 {
|
||||
// Trim to size.
|
||||
@ -506,7 +479,7 @@ class MachineDocument:
|
||||
}
|
||||
|
||||
index = index + 1
|
||||
}
|
||||
}*/
|
||||
|
||||
if didInstallRom {
|
||||
if self.missingROMs.count == 0 {
|
||||
|
@ -33,20 +33,14 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
CSMachineKeyboardInputModeJoystick,
|
||||
};
|
||||
|
||||
@interface CSMissingROM: NSObject
|
||||
@property (nonatomic, readonly, nonnull) NSString *machineName;
|
||||
@property (nonatomic, readonly, nonnull) NSString *fileName;
|
||||
@property (nonatomic, readonly, nullable) NSString *descriptiveName;
|
||||
@property (nonatomic, readonly) NSUInteger size;
|
||||
@property (nonatomic, readonly, nonnull) NSArray<NSNumber *> *crc32s;
|
||||
@end
|
||||
|
||||
// Deliberately low; to ensure CSMachine has been declared as an @class already.
|
||||
#import "CSAtari2600.h"
|
||||
#import "CSZX8081.h"
|
||||
|
||||
@interface CSMachine : NSObject
|
||||
|
||||
+ (BOOL)attemptInstallROM:(NSURL *)url;
|
||||
|
||||
- (nonnull instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/*!
|
||||
@ -56,7 +50,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
@param missingROMs An array that is filled with a list of ROMs that the machine requested but which
|
||||
were not found; populated only if this `init` has failed.
|
||||
*/
|
||||
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result missingROMs:(nullable inout NSMutableArray<CSMissingROM *> *)missingROMs NS_DESIGNATED_INITIALIZER;
|
||||
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result missingROMs:(nullable inout NSString *)missingROMs NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (float)idealSamplingRateFromRange:(NSRange)range;
|
||||
@property (readonly, getter=isStereo) BOOL stereo;
|
||||
|
@ -74,28 +74,6 @@ struct ActivityObserver: public Activity::Observer {
|
||||
__unsafe_unretained CSMachine *machine;
|
||||
};
|
||||
|
||||
@interface CSMissingROM (/*Mutability*/)
|
||||
@property (nonatomic, nonnull, copy) NSString *machineName;
|
||||
@property (nonatomic, nonnull, copy) NSString *fileName;
|
||||
@property (nonatomic, nullable, copy) NSString *descriptiveName;
|
||||
@property (nonatomic, readwrite) NSUInteger size;
|
||||
@property (nonatomic, copy) NSArray<NSNumber *> *crc32s;
|
||||
@end
|
||||
|
||||
@implementation CSMissingROM
|
||||
|
||||
@synthesize machineName=_machineName;
|
||||
@synthesize fileName=_fileName;
|
||||
@synthesize descriptiveName=_descriptiveName;
|
||||
@synthesize size=_size;
|
||||
@synthesize crc32s=_crc32s;
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"%@/%@, %lu bytes, CRCs: %@", _fileName, _descriptiveName, (unsigned long)_size, _crc32s];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSMachine {
|
||||
SpeakerDelegate _speakerDelegate;
|
||||
ActivityObserver _activityObserver;
|
||||
@ -126,7 +104,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
NSMutableArray<dispatch_block_t> *_inputEvents;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result missingROMs:(inout NSMutableArray<CSMissingROM *> *)missingROMs {
|
||||
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result missingROMs:(inout NSString *)missingROMs {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_analyser = result;
|
||||
@ -783,4 +761,8 @@ struct ActivityObserver: public Activity::Observer {
|
||||
_timer = nil;
|
||||
}
|
||||
|
||||
+ (BOOL)attemptInstallROM:(NSURL *)url {
|
||||
return CSInstallROM(url);
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -9,3 +9,4 @@
|
||||
#include "ROMMachine.hpp"
|
||||
|
||||
ROMMachine::ROMFetcher CSROMFetcher(ROM::Request *missing = nullptr);
|
||||
BOOL CSInstallROM(NSURL *);
|
||||
|
@ -11,24 +11,75 @@
|
||||
|
||||
#import "NSBundle+DataResource.h"
|
||||
#import "NSData+StdVector.h"
|
||||
#import "NSData+CRC32.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
NSString *directoryFor(const ROM::Description &description) {
|
||||
return [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:description.machine_name.c_str()]];
|
||||
}
|
||||
|
||||
NSArray<NSURL *> *urlsFor(const ROM::Description &description, const std::string &file_name) {
|
||||
NSMutableArray<NSURL *> *const urls = [[NSMutableArray alloc] init];
|
||||
NSArray<NSURL *> *const supportURLs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask];
|
||||
NSString *const subdirectory = directoryFor(description);
|
||||
|
||||
for(NSURL *supportURL in supportURLs) {
|
||||
[urls addObject:[[supportURL URLByAppendingPathComponent:subdirectory]
|
||||
URLByAppendingPathComponent:[NSString stringWithUTF8String:file_name.c_str()]]];
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BOOL CSInstallROM(NSURL *url) {
|
||||
NSData *const data = [NSData dataWithContentsOfURL:url];
|
||||
if(!data) return NO;
|
||||
|
||||
// Try for a direct CRC match.
|
||||
std::optional<ROM::Description> target_description;
|
||||
target_description = ROM::Description::from_crc(uint32_t(data.crc32.integerValue));
|
||||
|
||||
// See whether there's an acceptable trimming that creates a CRC match.
|
||||
if(!target_description) {
|
||||
const std::vector<ROM::Description> descriptions = ROM::all_descriptions();
|
||||
for(const auto &description: descriptions) {
|
||||
if(description.size > data.length) continue;
|
||||
|
||||
NSData *const trimmedData = [data subdataWithRange:NSMakeRange(0, description.size)];
|
||||
if(description.crc32s.find(uint32_t(trimmedData.crc32.unsignedIntValue)) != description.crc32s.end()) {
|
||||
target_description = description;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no destination was found, stop.
|
||||
if(!target_description) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Copy the data to its destination and report success.
|
||||
NSURL *const targetURL = [urlsFor(*target_description, target_description->file_names[0]) firstObject];
|
||||
[data writeToURL:targetURL atomically:YES];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
ROMMachine::ROMFetcher CSROMFetcher(ROM::Request *missing) {
|
||||
return [missing] (const ROM::Request &roms) -> ROM::Map {
|
||||
NSArray<NSURL *> *const supportURLs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask];
|
||||
|
||||
ROM::Map results;
|
||||
for(const auto &description: roms.all_descriptions()) {
|
||||
for(const auto &file_name: description.file_names) {
|
||||
NSData *fileData;
|
||||
NSString *const subdirectory = [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:description.machine_name.c_str()]];
|
||||
|
||||
// Check for this file first within the application support directories.
|
||||
for(NSURL *supportURL in supportURLs) {
|
||||
NSURL *const fullURL = [[supportURL URLByAppendingPathComponent:subdirectory]
|
||||
URLByAppendingPathComponent:[NSString stringWithUTF8String:file_name.c_str()]];
|
||||
fileData = [NSData dataWithContentsOfURL:fullURL];
|
||||
for(NSURL *fileURL in urlsFor(description, file_name)) {
|
||||
fileData = [NSData dataWithContentsOfURL:fileURL];
|
||||
if(fileData) break;
|
||||
}
|
||||
|
||||
@ -37,7 +88,7 @@ ROMMachine::ROMFetcher CSROMFetcher(ROM::Request *missing) {
|
||||
fileData = [[NSBundle mainBundle]
|
||||
dataForResource:[NSString stringWithUTF8String:file_name.c_str()]
|
||||
withExtension:nil
|
||||
subdirectory:subdirectory];
|
||||
subdirectory:directoryFor(description)];
|
||||
}
|
||||
|
||||
// Store an appropriate result.
|
||||
@ -47,7 +98,9 @@ ROMMachine::ROMFetcher CSROMFetcher(ROM::Request *missing) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: sever all found ROMs from roms and store to missing, if provided.
|
||||
if(missing) {
|
||||
*missing = roms.subtract(results);
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user