Files
OpenEmuKit/Source/OEXPCGameCoreManager.m
T

239 lines
8.9 KiB
Objective-C

// Copyright (c) 2020, OpenEmu Team
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the OpenEmu Team nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY OpenEmu Team ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import "OEXPCGameCoreManager.h"
#import "OEXPCGameCoreHelper.h"
#import "OECorePlugin.h"
#import "OEGameCoreManager_Internal.h"
#import "OESystemPlugin.h"
#import "OEThreadProxy.h"
#import "NSXPCConnection+HelperApp.h"
#import "OEGameStartupInfo.h"
#import "OELogging.h"
@interface OEXPCGameCoreManager ()
{
NSXPCConnection *_helperConnection;
OEThreadProxy *_gameCoreOwnerProxy;
}
@property(nonatomic, strong) id<OEXPCGameCoreHelper> gameCoreHelper;
@end
@implementation OEXPCGameCoreManager
@dynamic gameCoreHelper;
- (NSDictionary<NSString *, NSString *> *)infoDictionary {
id obj = [NSBundle.mainBundle objectForInfoDictionaryKey:@"OpenEmuKit"];
if ([obj isKindOfClass:NSDictionary.class]) {
return obj;
}
return nil;
}
- (NSString *)serviceName
{
return [self.infoDictionary objectForKey:@"XPCBrokerServiceName"];
}
- (NSURL *)executableURL
{
NSString * name = [self.infoDictionary objectForKey:@"XPCHelperExecutableName"];
return [NSBundle.mainBundle URLForAuxiliaryExecutable:name];
}
- (void)loadROMWithCompletionHandler:(OEStartupCompletionHandler)completionHandler
{
NSError *error = nil;
_helperConnection = [NSXPCConnection connectionWithServiceName:self.serviceName executableURL:self.executableURL error:&error];
if(_helperConnection == nil)
{
os_log_error(OE_LOG_HELPER, "No listener endpoint for identifier: %@", self.executableURL);
if (error == nil)
{
error = [NSError errorWithDomain:OEGameCoreErrorDomain
code:OEGameCoreCouldNotLoadROMError
userInfo:nil];
}
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
completionHandler(error);
});
// There's no listener endpoint, so don't bother trying to create an NSXPCConnection.
// Returning now since calling initWithListenerEndpoint: and passing it nil results in a memory leak.
// Also, there's no point in trying to get the gameCoreHelper if there's no _helperConnection.
return;
}
__weak OEXPCGameCoreManager *weakSelf = self;
[_helperConnection setInvalidationHandler:^{
OEXPCGameCoreManager *strongSelf = weakSelf;
if (strongSelf)
[strongSelf _notifyGameCoreDidTerminate];
}];
_gameCoreOwnerProxy = [OEThreadProxy threadProxyWithTarget:[self gameCoreOwner] thread:[NSThread mainThread]];
[_helperConnection setExportedInterface:[NSXPCInterface interfaceWithProtocol:@protocol(OEGameCoreOwner)]];
[_helperConnection setExportedObject:_gameCoreOwnerProxy];
NSXPCInterface *intf = [NSXPCInterface interfaceWithProtocol:@protocol(OEXPCGameCoreHelper)];
// startup
[intf setClasses:[NSSet setWithObject:OEGameStartupInfo.class]
forSelector:@selector(loadWithStartupInfo:completionHandler:)
argumentIndex:0
ofReply:NO];
[_helperConnection setRemoteObjectInterface:intf];
[_helperConnection resume];
__block void *gameCoreHelperPointer;
id<OEXPCGameCoreHelper> gameCoreHelper =
[_helperConnection remoteObjectProxyWithErrorHandler:
^(NSError *error)
{
os_log_error(OE_LOG_HELPER, "Helper Connection (%p) failed with error: %{public}@",
gameCoreHelperPointer, error);
[self stop];
}];
gameCoreHelperPointer = (__bridge void *)gameCoreHelper;
if(gameCoreHelper == nil) return;
[gameCoreHelper loadWithStartupInfo:self.startupInfo completionHandler:
^(NSError *error)
{
if(error != nil)
{
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
completionHandler(error);
[self stop];
});
// There's no listener endpoint, so don't bother trying to create an NSXPCConnection.
// Returning now since calling initWithListenerEndpoint: and passing it nil results in a memory leak.
return;
}
[self setGameCoreHelper:gameCoreHelper];
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
completionHandler(nil);
});
}];
}
- (void)loadROMWithCompletionHandler:(void(^)(void))completionHandler errorHandler:(void(^)(NSError *))errorHandler
{
NSError *error = nil;
_helperConnection = [NSXPCConnection connectionWithServiceName:self.serviceName executableURL:self.executableURL error:&error];
if(_helperConnection == nil)
{
os_log_error(OE_LOG_HELPER, "No listener endpoint for identifier: %@", self.executableURL);
if (error == nil)
{
error = [NSError errorWithDomain:OEGameCoreErrorDomain
code:OEGameCoreCouldNotLoadROMError
userInfo:nil];
}
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
errorHandler(error);
});
// There's no listener endpoint, so don't bother trying to create an NSXPCConnection.
// Returning now since calling initWithListenerEndpoint: and passing it nil results in a memory leak.
// Also, there's no point in trying to get the gameCoreHelper if there's no _helperConnection.
return;
}
__weak OEXPCGameCoreManager *weakSelf = self;
[_helperConnection setInvalidationHandler:^{
OEXPCGameCoreManager *strongSelf = weakSelf;
if (strongSelf)
[strongSelf _notifyGameCoreDidTerminate];
}];
_gameCoreOwnerProxy = [OEThreadProxy threadProxyWithTarget:[self gameCoreOwner] thread:[NSThread mainThread]];
[_helperConnection setExportedInterface:[NSXPCInterface interfaceWithProtocol:@protocol(OEGameCoreOwner)]];
[_helperConnection setExportedObject:_gameCoreOwnerProxy];
NSXPCInterface *intf = [NSXPCInterface interfaceWithProtocol:@protocol(OEXPCGameCoreHelper)];
// startup
[intf setClasses:[NSSet setWithObject:OEGameStartupInfo.class]
forSelector:@selector(loadWithStartupInfo:completionHandler:)
argumentIndex:0
ofReply:NO];
[_helperConnection setRemoteObjectInterface:intf];
[_helperConnection resume];
__block void *gameCoreHelperPointer;
id<OEXPCGameCoreHelper> gameCoreHelper =
[_helperConnection remoteObjectProxyWithErrorHandler:
^(NSError *error)
{
os_log_error(OE_LOG_HELPER, "Helper Connection (%p) failed with error: %{public}@",
gameCoreHelperPointer, error);
[self stop];
}];
gameCoreHelperPointer = (__bridge void *)gameCoreHelper;
if(gameCoreHelper == nil) return;
[gameCoreHelper loadWithStartupInfo:self.startupInfo completionHandler:
^(NSError *error)
{
if(error != nil)
{
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
errorHandler(error);
[self stop];
});
// There's no listener endpoint, so don't bother trying to create an NSXPCConnection.
// Returning now since calling initWithListenerEndpoint: and passing it nil results in a memory leak.
return;
}
[self setGameCoreHelper:gameCoreHelper];
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, completionHandler);
}];
}
- (void)stop {
[self setGameCoreHelper:nil];
_gameCoreOwnerProxy = nil;
[_helperConnection invalidate];
}
@end