diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift
index c4b42aa0a..d9962a42f 100644
--- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift	
+++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift	
@@ -153,11 +153,14 @@ class MachineDocument:
 
 	// MARK: configuring
 	func configureAs(_ analysis: CSStaticAnalyser) {
-		if let machine = CSMachine(analyser: analysis) {
+		let missingROMs = NSMutableArray()
+		if let machine = CSMachine(analyser: analysis, missingROMs: missingROMs) {
 			self.machine = machine
 			self.optionsPanelNibName = analysis.optionsPanelNibName
 			setupMachineOutput()
 			setupActivityDisplay()
+		} else {
+			Swift.print(missingROMs)
 		}
 	}
 
diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h
index 5f1bd5da0..0a6e7496c 100644
--- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h	
+++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h	
@@ -33,6 +33,13 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
 	CSMachineKeyboardInputModeJoystick
 };
 
+@interface CSMissingROM: NSObject
+@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"
@@ -45,8 +52,10 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
 	Initialises an instance of CSMachine.
 
 	@param result The CSStaticAnalyser result that describes the machine needed.
+	@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 NS_DESIGNATED_INITIALIZER;
+- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result missingROMs:(nullable inout NSMutableArray<CSMissingROM *> *)missingROMs NS_DESIGNATED_INITIALIZER;
 
 - (void)runForInterval:(NSTimeInterval)interval;
 
diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm
index 1aa0fab1f..59476a9c0 100644
--- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm	
+++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm	
@@ -74,6 +74,58 @@ struct ActivityObserver: public Activity::Observer {
 	__unsafe_unretained CSMachine *machine;
 };
 
+@interface CSMissingROM (Mutability)
+@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 {
+	NSString *_fileName;
+	NSString *_descriptiveName;
+	NSUInteger _size;
+	NSArray<NSNumber *> *_crc32s;
+}
+
+- (NSString *)fileName {
+	return _fileName;
+}
+
+- (void)setFileName:(NSString *)fileName {
+	_fileName = [fileName copy];
+}
+
+- (NSString *)descriptiveName {
+	return _descriptiveName;
+}
+
+- (void)setDescriptiveName:(NSString *)descriptiveName {
+	_descriptiveName = [descriptiveName copy];
+}
+
+- (NSUInteger)size {
+	return _size;
+}
+
+- (void)setSize:(NSUInteger)size {
+	_size = size;
+}
+
+- (NSArray<NSNumber *> *)crc32s {
+	return _crc32s;
+}
+
+- (void)setCrc32s:(NSArray<NSNumber *> *)crc32s {
+	_crc32s = [crc32s copy];
+}
+
+- (NSString *)description {
+	return [NSString stringWithFormat:@"%@/%@, %@ bytes, CRCs: %@", _fileName, _descriptiveName, @(_size), _crc32s];
+}
+
+@end
+
 @implementation CSMachine {
 	SpeakerDelegate _speakerDelegate;
 	ActivityObserver _activityObserver;
@@ -90,7 +142,7 @@ struct ActivityObserver: public Activity::Observer {
 	std::unique_ptr<Outputs::Display::OpenGL::ScanTarget> _scanTarget;
 }
 
-- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result {
+- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result missingROMs:(inout NSMutableArray<CSMissingROM *> *)missingROMs {
 	self = [super init];
 	if(self) {
 		_analyser = result;
@@ -98,7 +150,28 @@ struct ActivityObserver: public Activity::Observer {
 		Machine::Error error;
 		std::vector<ROMMachine::ROM> missing_roms;
 		_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(&missing_roms), error));
-		if(!_machine) return nil;
+		if(!_machine) {
+			for(const auto &missing_rom : missing_roms) {
+				CSMissingROM *rom = [[CSMissingROM alloc] init];
+
+				// Copy/convert the primitive fields.
+				rom.fileName = [NSString stringWithUTF8String:missing_rom.file_name.c_str()];
+				rom.descriptiveName = [NSString stringWithUTF8String:missing_rom.descriptive_name.c_str()];
+				rom.size = missing_rom.size;
+
+				// Convert the CRC list.
+				NSMutableArray<NSNumber *> *crc32s = [[NSMutableArray alloc] init];
+				for(const auto &crc : missing_rom.crc32s) {
+					[crc32s addObject:@(crc)];
+				}
+				rom.crc32s = [crc32s copy];
+
+				// Add to the missing list.
+				[missingROMs addObject:rom];
+			}
+
+			return nil;
+		}
 
 		_inputMode =
 			(_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive())