1
0
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:
Thomas Harte 2021-06-06 14:24:38 -04:00
parent b0f551c307
commit 5acd97c860
7 changed files with 101 additions and 80 deletions

View File

@ -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;

View File

@ -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() {}

View File

@ -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 {

View File

@ -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;

View File

@ -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

View File

@ -9,3 +9,4 @@
#include "ROMMachine.hpp"
ROMMachine::ROMFetcher CSROMFetcher(ROM::Request *missing = nullptr);
BOOL CSInstallROM(NSURL *);

View File

@ -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;
};