/* * PDP-8/E Simulator * * Copyright © 1994-2015 Bernhard Baehr * * PluginManager.m - Manager for I/O Device Plugins * * This file is part of PDP-8/E Simulator. * * PDP-8/E Simulator is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #import #import "PluginManager.h" #import "PluginAPI.h" #import "PDP8.h" #import "SkipController.h" #import "IOFlagController.h" #import "Assembler.h" #import "Disassembler.h" #import "OctalFormatter.h" #import "HelpMenuManager.h" #import "Unicode.h" #import "NSFileManager+Additions.h" #define PLUGIN_EXTENSION @"pdp8Plugin" #define PLUGIN_APPSUPPORT_PATH @"Application Support/PDP-8:E Simulator" #define PLUGIN_NEXTTOAPP_PATH @"../PDP-8:E Simulator Plugins" @implementation PluginManager - (BOOL) canInstallIOTs:(NSArray *)mnemonics withIOAddresses:(NSArray *)ioAddresses noLoadMessage:(NSString *)noLoadMessage { NSString *ioAddress; if (! mnemonics || ! [mnemonics isKindOfClass:[NSArray class]]) { NSRunAlertPanel (NSLocalizedString( @"Invalid IOT information in the I/O description of the plug-in", @""), noLoadMessage, nil, nil, nil); return NO; } if (! ioAddresses || ! [ioAddresses isKindOfClass:[NSArray class]]) { NSRunAlertPanel (NSLocalizedString( @"Invalid I/O address information in the I/O description of the plug-in.", @""), noLoadMessage, nil, nil, nil); return NO; } if ([mnemonics count] != 8 * [ioAddresses count]) { NSRunAlertPanel (NSLocalizedString( @"The number of IOTs does not match number of I/O addresses in the I/O description " "of the plug-in.", @""), noLoadMessage, nil, nil, nil); return NO; } NSEnumerator *enumerator = [ioAddresses objectEnumerator]; while ((ioAddress = [enumerator nextObject])) { NSNumber *number; if (! [[OctalFormatter formatterWithBitMask:077 wildcardAllowed:NO] getObjectValue:&number forString:ioAddress errorDescription:nil]) { NSRunAlertPanel ([NSString stringWithFormat:NSLocalizedString( @"Invalid I/O address %C%@%C in the I/O description of the plug-in.", @""), UNICODE_LEFT_DOUBLEQUOTE, ioAddress, UNICODE_RIGHT_DOUBLEQUOTE], noLoadMessage, nil, nil, nil); return NO; } int addr = [number intValue]; if (! [pdp8 isIOAddressAvailable:addr]) { NSRunAlertPanel ([NSString stringWithFormat:NSLocalizedString( @"The I/O address %2.2o requested by the plug-in is already in use.", @""), addr], noLoadMessage, nil, nil, nil); return NO; } } return YES; } - (void) installIOTs:(NSArray *)mnemonics withAddresses:(NSArray *)ioAddresses forPlugin:(PDP8Plugin *)plugin { int i; NSString *ioAddress; int base = 0; NSEnumerator *ioAddrEnum = [ioAddresses objectEnumerator]; while ((ioAddress = [ioAddrEnum nextObject])) { NSNumber *number; [[OctalFormatter formatterWithBitMask:077 wildcardAllowed:NO] getObjectValue:&number forString:ioAddress errorDescription:nil]; int addr = [number intValue]; [pdp8 setPluginPointer:plugin forIOAddress:addr]; NSArray *iots = [plugin iotsForAddress:addr]; if (iots) { NSArray *skiptests = [plugin skiptestsForAddress:addr]; int opcode = 06000 | (addr << 3); for (i = 0; i < 8; i++) { NSValue *iot = [iots objectAtIndex:i]; [pdp8 setIOT:iot forOpcode:opcode | i]; NSValue *skiptest = skiptests ? [skiptests objectAtIndex:i] : nil; [skipController addSkiptest:skiptest forInstruction:iot]; NSString *mnemonic = [mnemonics objectAtIndex:base + i]; [[Assembler sharedAssembler] addMnemonic:mnemonic forIOT:opcode | i]; [[Disassembler sharedDisassembler] addMnemonic:mnemonic forIOT:opcode | i]; } } base += 8; } } - (BOOL) canInstallIOFlags:(NSArray *)ioFlags noLoadMessage:(NSString *)noLoadMessage { if (! ioFlags || ! [ioFlags isKindOfClass:[NSArray class]]) { NSRunAlertPanel (NSLocalizedString( @"Invalid I/O flag information in the I/O description of the plug-in.", @""), noLoadMessage, nil, nil, nil); return NO; } if ([ioFlags count] > [ioFlagController numberOfAvailableFlags]) { NSRunAlertPanel (NSLocalizedString(@"There are not enough I/O flags available.", @""), noLoadMessage, nil, nil, nil); return NO; } return YES; } - (void) installIOFlags:(NSArray *)ioFlags forPlugin:(PDP8Plugin *)plugin { NSString *ioFlagName; NSEnumerator *enumerator = [ioFlags objectEnumerator]; while ((ioFlagName = [enumerator nextObject])) [plugin setIOFlag:[ioFlagController addIODevice:ioFlagName] forIOFlagName:ioFlagName]; } - (void) resetAllDevices { PDP8Plugin *plugin; NSEnumerator *enumerator = [pluginInstances objectEnumerator]; while ((plugin = [enumerator nextObject])) [plugin resetDevice]; } - (NSString *) noLoadMessage:(NSString *)pluginName { return [NSString stringWithFormat:NSLocalizedString(@"The plug-in %C%@%C will not be loaded.", @""), UNICODE_LEFT_DOUBLEQUOTE, pluginName, UNICODE_RIGHT_DOUBLEQUOTE]; } - (PDP8Plugin *) loadPlugin:(NSBundle *)bundle { PDP8Plugin *plugin = nil; Class principalClass = [bundle principalClass]; if (principalClass && [principalClass isSubclassOfClass:[PDP8Plugin class]]) { plugin = [[principalClass alloc] init]; if (plugin) { [plugin apiVersion]; // plugin must overrides this method, otherwise we crash [plugin setBundle:bundle]; [plugin setPDP8:pdp8]; NSDictionary *ioInfo = [plugin ioInformation]; NSArray *ioFlagNames = [ioInfo objectForKey:IO_INFO_IOFLAGS_KEY]; NSArray *ioAddresses = [ioInfo objectForKey:IO_INFO_IOADDRESSES_KEY]; NSArray *mnemonics = [ioInfo objectForKey:IO_INFO_IOTS_KEY]; if ([self canInstallIOFlags:ioFlagNames noLoadMessage:[self noLoadMessage:[plugin pluginName]]] && [self canInstallIOTs:mnemonics withIOAddresses:ioAddresses noLoadMessage:[self noLoadMessage:[plugin pluginName]]]) { [self installIOFlags:[ioInfo objectForKey:IO_INFO_IOFLAGS_KEY] forPlugin:plugin]; [self installIOTs:mnemonics withAddresses:ioAddresses forPlugin:plugin]; [plugin loadNibs]; [plugin pluginDidLoad]; [[HelpMenuManager sharedHelpMenuManager] addBundleHelp:bundle]; } else { [plugin release]; // unload is a 10.5 method (does nothing on 10.4), but we use the 10.4 SDK // so use performSelector: to avoid unknown message warning [bundle performSelector:@selector(unload)]; return nil; } } else { NSRunAlertPanel (NSLocalizedString(@"Cannot instantiate the plug-in.", @""), [self noLoadMessage:[[bundle bundlePath] lastPathComponent]], nil, nil, nil); } } else { NSRunAlertPanel (principalClass ? NSLocalizedString(@"The plug-in has an invalid principal class.", @"") : NSLocalizedString(@"Cannot determine the principal class of the plug-in.", @""), [self noLoadMessage:[[bundle bundlePath] lastPathComponent]], nil, nil, nil); } return [plugin autorelease]; } - (NSArray *) loadAllPlugins { NSEnumerator *searchPathEnum; NSString *path; NSString *bundleName; NSMutableArray *bundleSearchPaths = [NSMutableArray array]; NSMutableArray *plugins = [NSMutableArray array]; searchPathEnum = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask - NSSystemDomainMask, YES) objectEnumerator]; while ((path = [searchPathEnum nextObject])) [bundleSearchPaths addObject:[path stringByAppendingPathComponent:PLUGIN_APPSUPPORT_PATH]]; [bundleSearchPaths addObject:[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:PLUGIN_NEXTTOAPP_PATH]]; [bundleSearchPaths addObject:[[NSBundle mainBundle] builtInPlugInsPath]]; searchPathEnum = [bundleSearchPaths objectEnumerator]; while ((path = [searchPathEnum nextObject])) { NSDirectoryEnumerator *bundlePathEnum = [[NSFileManager defaultManager] enumeratorAtPath:path]; while (bundlePathEnum && (bundleName = [bundlePathEnum nextObject])) { [bundlePathEnum skipDescendents]; if ([[bundleName pathExtension] isEqualToString:PLUGIN_EXTENSION]) { NSBundle *bundle = [NSBundle bundleWithPath: [[NSFileManager defaultManager] resolveAliasPath: [path stringByAppendingPathComponent:bundleName]]]; if (bundle) { PDP8Plugin *plugin = [self loadPlugin:bundle]; if (plugin) [plugins addObject:plugin]; } else NSRunAlertPanel ( NSLocalizedString(@"Loading of the plug-in bundle failed.", @""), [self noLoadMessage:bundleName], nil, nil, nil); } } } return plugins; } - (void) notifyApplicationWillFinishLaunching:(NSNotification *)notification { pluginInstances = [[self loadAllPlugins] retain]; } - (void) notifyApplicationDidFinishLaunching:(NSNotification *)notification { [[NSNotificationCenter defaultCenter] postNotificationName:PLUGINS_LOADED_NOTIFICATION object:nil]; } - (void) awakeFromNib { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyApplicationWillFinishLaunching:) name:NSApplicationWillFinishLaunchingNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyApplicationDidFinishLaunching:) name:NSApplicationDidFinishLaunchingNotification object:nil]; } @end