Files
2020-09-28 14:10:44 -07:00

689 lines
22 KiB
Objective-C

/*
Copyright (c) 2016, 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 "ViceGameCore.h"
#import "OEC64SystemResponderClient.h"
#import <OpenGL/gl.h>
#import <Carbon/Carbon.h>
#import "api.h"
#import "C64.h"
#import "C64_archdep.h"
#import "C64dtv.h"
#import "C64dtv_archdep.h"
#import "C128.h"
#import "C128_archdep.h"
#import "Vic20.h"
#import "Vic20_archdep.h"
const NSEventModifierFlags OENSEventModifierFlagFunctionKey = 1 << 24;
@interface ViceGameCore() <OEC64SystemResponderClient, C64Delegate, C128Delegate, Vic20Delegate>
{
NSArray *_availableDisplayModes;
BOOL _isJoystickPortSwapped;
BOOL _runstopLock;
C64dtv *_c64dtv;
C64 *_c64;
C128 *_c128;
Vic20 *_vic20;
NSArray<NSString *> *_sysfilePathList;
uint32_t *_buffer;
OEIntSize _bufferSize;
OEIntRect _screenRect;
NSTimeInterval _frameInterval;
NSString *_viceCore;
}
- (void)initializeEmulator;
@end
@implementation ViceGameCore
- (id)init
{
if([[self systemIdentifier] isEqualToString:@"openemu.system.vic20"]){
_viceCore = @"Vic20";
} else if([[self systemIdentifier] isEqualToString:@"openemu.system.c128"]){
_viceCore = @"C128";
}else if([[self systemIdentifier] isEqualToString:@"openemu.system.c64dtv"]){
_viceCore = @"DTV";
} else {
_viceCore = @"C64";
}
_viceCore = @"DTV";
if((self = [super init])) {
if ([_viceCore isEqualToString:@"Vic20"]){
_vic20 = Vic20.shared;
_vic20.delegate = self;
_bufferSize = OEIntSizeMake(900, 700);
NSRect f = _vic20.videoFrame;
_screenRect = OEIntRectMake(f.origin.x, f.origin.y, f.size.width, f.size.height);
_frameInterval = _vic20.videoFrequency;
} else if([_viceCore isEqualToString:@"C128"]){
_c128 = C128.shared;
_c128.delegate = self;
_bufferSize = OEIntSizeMake(384, 272);
NSRect f = _c128.videoFrame;
_screenRect = OEIntRectMake(f.origin.x, f.origin.y, f.size.width, f.size.height);
_frameInterval = _c128.videoFrequency;
} else if ([_viceCore isEqualToString:@"DTV"]){
_c64dtv = C64dtv.shared;
_c64dtv.delegate = self;
_bufferSize = OEIntSizeMake(384, 272);
NSRect f = _c64dtv.videoFrame;
_screenRect = OEIntRectMake(f.origin.x, f.origin.y, f.size.width, f.size.height);
_frameInterval = _c64dtv.videoFrequency;
} else {
_c64 = C64.shared;
_c64.delegate = self;
_bufferSize = OEIntSizeMake(384, 272);
NSRect f = _c64.videoFrame;
_screenRect = OEIntRectMake(f.origin.x, f.origin.y, f.size.width, f.size.height);
_frameInterval = _c64.videoFrequency;
}
}
return self;
}
#pragma mark - C64Delegate
- (void)updateAudioBuffer:(const int16_t *)buffer samples:(NSInteger)samples {
[[self audioBufferAtIndex:0] write:buffer maxLength:samples * 2];
}
- (void)canvasWillResizeWidth:(NSUInteger)width height:(NSUInteger)height {
if ([_viceCore isEqualToString:@"Vic20"]){
NSRect f = _vic20.videoFrame;
_screenRect = OEIntRectMake(f.origin.x, f.origin.y, f.size.width, f.size.height);
_frameInterval = _vic20.videoFrequency;
} else if([_viceCore isEqualToString:@"C128"]){
NSRect f = _c128.videoFrame;
_screenRect = OEIntRectMake(f.origin.x, f.origin.y, f.size.width, f.size.height);
_frameInterval = _c128.videoFrequency;
}else if ([_viceCore isEqualToString:@"DTV"]){
NSRect f = _c64dtv.videoFrame;
_screenRect = OEIntRectMake(f.origin.x, f.origin.y, f.size.width, f.size.height);
_frameInterval = _c64dtv.videoFrequency;
} else {
NSRect f = _c64.videoFrame;
_screenRect = OEIntRectMake(f.origin.x, f.origin.y, f.size.width, f.size.height);
_frameInterval = _c64.videoFrequency;
}
}
#pragma mark - emulation
- (void)setupEmulation {
if ([_viceCore isEqualToString:@"Vic20"]){
_vic20.delegate = self;
} else if([_viceCore isEqualToString:@"C128"]){
_c128.delegate = self;
}else if ([_viceCore isEqualToString:@"DTV"]){
_c64dtv.delegate = self;
} else {
_c64.delegate = self;
}
[super setupEmulation];
}
- (void)stopEmulation {
if ([_viceCore isEqualToString:@"Vic20"]){
[_vic20 resetMachineHard];
_vic20.delegate = nil;
} else if([_viceCore isEqualToString:@"C128"]){
[_c128 resetMachineHard];
_c128.delegate = nil;
}else if ([_viceCore isEqualToString:@"DTV"]){
[_c64dtv resetMachineHard];
_c64dtv.delegate = nil;
} else {
[_c64 resetMachineHard];
_c64.delegate = nil;
}
[super stopEmulation];
}
- (BOOL)loadFileAtPath:(NSString *)path error:(NSError **)error
{
[self initializeEmulator];
// return true;
if ([_viceCore isEqualToString:@"Vic20"]){
return [_vic20 autostartImageAtURL:[NSURL fileURLWithPath:path] error:error];
} else if([_viceCore isEqualToString:@"C128"]){
return [_c128 autostartImageAtURL:[NSURL fileURLWithPath:path] error:error];
}else if ([_viceCore isEqualToString:@"DTV"]){
return [_c64dtv autostartImageAtURL:[NSURL fileURLWithPath:path] error:error];
} else {
return [_c64 autostartImageAtURL:[NSURL fileURLWithPath:path] error:error];
}
}
- (void)insertFileAtURL:(NSURL *)url completionHandler:(void(^)(BOOL success, NSError *error))block
{
if ([_viceCore isEqualToString:@"Vic20"]){
Vic20ImageType type = [_vic20 imageTypeAtURL:url];
} else if([_viceCore isEqualToString:@"C128"]){
C128ImageType type = [_c128 imageTypeAtURL:url];
}else if ([_viceCore isEqualToString:@"DTV"]){
C64dtvImageType type = [_c64dtv imageTypeAtURL:url];
} else {
C64ImageType type = [_c64 imageTypeAtURL:url];
}
block(YES, nil);
}
- (void)resetEmulation
{
if ([_viceCore isEqualToString:@"Vic20"]){
[_vic20 resetMachineHard];
} else if([_viceCore isEqualToString:@"C128"]){
[_c128 resetMachineHard];
}else if ([_viceCore isEqualToString:@"DTV"]){
[_c64dtv resetMachineHard];
} else {
[_c64 resetMachineHard];
}
}
- (void)executeFrame
{
if ([_viceCore isEqualToString:@"Vic20"]){
[_vic20 execute];
} else if([_viceCore isEqualToString:@"C128"]){
[_c128 execute];
}else if ([_viceCore isEqualToString:@"DTV"]){
[_c64dtv execute];
} else {
[_c64 execute];
}
}
- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void(^)(BOOL success, NSError *error))block
{
NSError *err = nil;
BOOL res = false;
if ([_viceCore isEqualToString:@"Vic20"]){
res = [_vic20 saveStateFromFileAtPath:fileName error:&err];
} else if([_viceCore isEqualToString:@"C128"]){
res = [_c128 saveStateFromFileAtPath:fileName error:&err];
}else if ([_viceCore isEqualToString:@"DTV"]){
res = [_c64dtv saveStateFromFileAtPath:fileName error:&err];
} else {
res = [_c64 saveStateFromFileAtPath:fileName error:&err];
}
block(res, err);
}
- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void(^)(BOOL success, NSError *error))block
{
NSError *err = nil;
BOOL res = false;
if ([_viceCore isEqualToString:@"Vic20"]){
res = [_vic20 loadStateFromFileAtPath:fileName error:&err];
} else if([_viceCore isEqualToString:@"C128"]){
res = [_c64 loadStateFromFileAtPath:fileName error:&err];
}else if ([_viceCore isEqualToString:@"DTV"]){
res = [_c64dtv loadStateFromFileAtPath:fileName error:&err];
} else {
res = [_c64 loadStateFromFileAtPath:fileName error:&err];
}
block(res, err);
}
- (NSTimeInterval)frameInterval {
return _frameInterval;
}
- (const void*)getVideoBufferWithHint:(void *)hint {
_buffer = hint;
if ([_viceCore isEqualToString:@"Vic20"]){
[_vic20 setBuffer:hint size:NSSizeFromOEIntSize(_bufferSize)];
} else if([_viceCore isEqualToString:@"C128"]){
[_c128 setBuffer:hint size:NSSizeFromOEIntSize(_bufferSize)];
}else if ([_viceCore isEqualToString:@"DTV"]){
[_c64dtv setBuffer:hint size:NSSizeFromOEIntSize(_bufferSize)];
} else {
[_c64 setBuffer:hint size:NSSizeFromOEIntSize(_bufferSize)];
}
return hint;
}
- (OEIntSize)bufferSize
{
return _bufferSize;
}
- (OEIntSize)aspectSize
{
// TODO
return OEIntSizeMake(4, 3);
}
- (OEIntRect)screenRect
{
return _screenRect;
}
- (GLenum)pixelFormat
{
return GL_BGRA;
}
- (GLenum)pixelType
{
return GL_UNSIGNED_INT_8_8_8_8_REV;
}
- (GLenum)internalPixelFormat
{
return GL_RGB8;
}
- (double)audioSampleRate
{
if ([_viceCore isEqualToString:@"Vic20"]){
return _vic20.audioFrequency;
} else if([_viceCore isEqualToString:@"C128"]){
return _c128.audioFrequency;
}else if ([_viceCore isEqualToString:@"DTV"]){
return _c64dtv.audioFrequency;
}
return _c64.audioFrequency;
}
- (NSUInteger)channelCount
{
if ([_viceCore isEqualToString:@"Vic20"]){
return _vic20.audioChannels;
} else if([_viceCore isEqualToString:@"C128"]){
return _c128.audioChannels;
}else if ([_viceCore isEqualToString:@"DTV"]){
return _c64dtv.audioChannels;
}
return _c64.audioChannels;
}
- (NSUInteger)audioBitDepth
{
return 16;
}
#pragma responder
- (oneway void)mouseMovedAtPoint:(OEIntPoint)point
{
}
- (oneway void)leftMouseDownAtPoint:(OEIntPoint)point
{
}
- (oneway void)leftMouseUp
{
}
- (oneway void)rightMouseDownAtPoint:(OEIntPoint)point
{
}
- (oneway void)rightMouseUp
{
}
KeyboardMod keyCodeToMod( unsigned short keyCode) {
switch (keyCode) {
case kVK_Shift:
return KeyboardModLShift;
case kVK_RightShift:
return KeyboardModRShift;
case kVK_Control:
return KeyboardModLCTRL;
case kVK_RightControl:
return KeyboardModRCTRL;
case kVK_Option:
return KeyboardModLALT;
case kVK_RightOption:
return KeyboardModRALT;
default:
return KeyboardModNone;
}
}
KeyboardMod flagsToMod(NSEventModifierFlags flags) {
KeyboardMod res = KeyboardModNone;
if (flags & NX_DEVICELSHIFTKEYMASK) {
res |= KeyboardModLShift;
}
if (flags & NX_DEVICERSHIFTKEYMASK) {
res |= KeyboardModRShift;
}
if (flags & NX_DEVICELCTLKEYMASK) {
res |= KeyboardModLCTRL;
}
if (flags & NX_DEVICERCTLKEYMASK) {
res |= KeyboardModRCTRL;
}
if (flags & NX_DEVICELALTKEYMASK) {
res |= KeyboardModLALT;
}
if (flags & NX_DEVICERALTKEYMASK) {
res |= KeyboardModRALT;
}
return res;
}
- (oneway void)keyDown:(unsigned short)keyCode characters:(NSString *)characters charactersIgnoringModifiers:(NSString *)charactersIgnoringModifiers flags:(NSEventModifierFlags)flags
{
if(keyCode == kVK_Function)
return;
switch (keyCode)
{
case kVK_F1:
case kVK_F2:
case kVK_F3:
case kVK_F4:
case kVK_F5:
case kVK_F6:
case kVK_F7:
case kVK_F8:
case kVK_F9:
case kVK_F10:
case kVK_F11:
case kVK_F12:
if (!(flags & OENSEventModifierFlagFunctionKey))
return;
}
KeyboardMod mod = flagsToMod(flags);
NSLog(@"keyDown: code=%03d, flags=%08x, mod=%08lx", keyCode, (uint32_t)flags, mod);
// Set the RunStopLock flag if Arrow up is pressed
// if (keyCode == kVK_UpArrow) RunStopLock = true;
if ([_viceCore isEqualToString:@"Vic20"]){
[_vic20 keyboardKeyDown:keyCode mod:mod];
} else if([_viceCore isEqualToString:@"C128"]){
[_c128 keyboardKeyDown:keyCode mod:mod];
}else if ([_viceCore isEqualToString:@"DTV"]){
[_c64dtv keyboardKeyDown:keyCode mod:mod];
} else {
[_c64 keyboardKeyDown:keyCode mod:mod];
}
}
- (oneway void)keyUp:(unsigned short)keyCode characters:(NSString *)characters charactersIgnoringModifiers:(NSString *)charactersIgnoringModifiers flags:(NSEventModifierFlags)flags
{
if(keyCode == kVK_Function)
return;
switch (keyCode)
{
case kVK_F1:
case kVK_F2:
case kVK_F3:
case kVK_F4:
case kVK_F5:
case kVK_F6:
case kVK_F7:
case kVK_F8:
case kVK_F9:
case kVK_F10:
case kVK_F11:
case kVK_F12:
if (!(flags & OENSEventModifierFlagFunctionKey))
return;
}
KeyboardMod mod = flagsToMod(flags);
NSLog(@"keyUp: code=%03d, flags=%08x, mod=%08lx", keyCode, (uint32_t)flags, mod);
// UnSet RunStopLock flag if Arrow up is released
// if (keyCode == 126) RunStopLock = false;
if ([_viceCore isEqualToString:@"Vic20"]){
[_vic20 keyboardKeyUp:keyCode mod:mod];
} else if([_viceCore isEqualToString:@"C128"]){
[_c128 keyboardKeyUp:keyCode mod:mod];
}else if ([_viceCore isEqualToString:@"DTV"]){
[_c64dtv keyboardKeyUp:keyCode mod:mod];
} else {
[_c64 keyboardKeyUp:keyCode mod:mod];
}
}
static uint8_t joystick_bits[] = {
[OEC64JoystickUp] = 0x01,
[OEC64JoystickDown] = 0x02,
[OEC64JoystickLeft] = 0x04,
[OEC64JoystickRight] = 0x08,
[OEC64ButtonFire] = 0x10
};
- (oneway void)swapJoysticks
{
// do port swap
_isJoystickPortSwapped = !_isJoystickPortSwapped;
}
- (oneway void)didPushC64Button:(OEC64Button)button forPlayer:(NSUInteger)player
{
// // Avoid a RunStop Lock if the Arrow key is pressed, and up is pushed on a Joystick
// if (RunStopLock && button == OEC64JoystickUp) keyboard_key_released(kVK_UpArrow);
//
if (button == OEC64ButtonJump) button = OEC64JoystickUp;
//
// Default to Port 2 for Player 1, since most C64 games use this
int port;
if (player == 1)
port = _isJoystickPortSwapped ? 1 : 2;
else
port = _isJoystickPortSwapped ? 2 : 1;
if ([_viceCore isEqualToString:@"Vic20"]){
[_vic20 joystickOrValue:joystick_bits[button] forPort:port];
} else if([_viceCore isEqualToString:@"C128"]){
[_c128 joystickOrValue:joystick_bits[button] forPort:port];
}else if ([_viceCore isEqualToString:@"DTV"]){
[_c64dtv joystickOrValue:joystick_bits[button] forPort:port];
} else {
[_c64 joystickOrValue:joystick_bits[button] forPort:port];
}
}
- (oneway void)didReleaseC64Button:(OEC64Button)button forPlayer:(NSUInteger)player
{
if (button == OEC64ButtonJump) button = OEC64JoystickUp;
//
// Default to Port 2 for Player 1, since most C64 games use this
int port;
if (player == 1)
port = _isJoystickPortSwapped ? 1 : 2;
else
port = _isJoystickPortSwapped ? 2 : 1;
uint8_t val = 0xff & ~joystick_bits[button];
if ([_viceCore isEqualToString:@"Vic20"]){
[_vic20 joystickAndValue:val forPort:port];
} else if([_viceCore isEqualToString:@"C128"]){
[_c128 joystickAndValue:val forPort:port];
}else if ([_viceCore isEqualToString:@"DTV"]){
[_c64dtv joystickAndValue:val forPort:port];
} else {
[_c64 joystickAndValue:val forPort:port];
}
}
- (NSArray <NSDictionary <NSString *, id> *> *)displayModes
{
if (_availableDisplayModes.count == 0)
{
_availableDisplayModes = [NSArray array];
NSArray <NSDictionary <NSString *, id> *> *availableModesWithDefault =
@[
@{ OEGameCoreDisplayModeNameKey : @"NTSC",
OEGameCoreDisplayModePrefKeyNameKey : @"format",
OEGameCoreDisplayModeStateKey : @NO
},
@{ OEGameCoreDisplayModeNameKey : @"PAL",
OEGameCoreDisplayModePrefKeyNameKey : @"format",
OEGameCoreDisplayModeStateKey : @NO
},
];
_availableDisplayModes = availableModesWithDefault;
}
return _availableDisplayModes;
}
- (void)changeDisplayWithMode:(NSString *)displayMode
{
if (_availableDisplayModes.count == 0)
[self displayModes];
NSMutableArray *tempModesArray = [NSMutableArray array];
for(NSDictionary *modeDict in _availableDisplayModes)
{
NSString *mode = modeDict[OEGameCoreDisplayModeNameKey];
NSString *pref = modeDict[OEGameCoreDisplayModePrefKeyNameKey];
BOOL state = NO;
if ([mode isEqualToString:displayMode])
state = YES;
[tempModesArray addObject:@{OEGameCoreDisplayModeNameKey : mode, OEGameCoreDisplayModeStateKey : @(state), OEGameCoreDisplayModePrefKeyNameKey : pref}];
}
if ([displayMode isEqualToString:@"NTSC"]) {
if ([_viceCore isEqualToString:@"Vic20"]){
_vic20.model = Vic20ModelNTSC;
} else if([_viceCore isEqualToString:@"C128"]){
_c128.model = C128ModelNTSC;
}else if ([_viceCore isEqualToString:@"DTV"]){
_c64dtv.model = DTVModelV3NTSC;
} else {
_c64.model = C64ModelNTSC;
}
} else {
if ([_viceCore isEqualToString:@"Vic20"]){
_vic20.model = Vic20ModelPAL;
} else if([_viceCore isEqualToString:@"C128"]){
_c128.model = C128ModelPAL;
}else if ([_viceCore isEqualToString:@"DTV"]){
_c64dtv.model = DTVModelV3PAL;
} else {
_c64.model = C64ModelPAL;
}
}
_availableDisplayModes = tempModesArray;
}
#pragma mark private
- (void)initializeEmulator
{
static bool initialized = false;
if (!initialized) {
NSString *bootPath = self.owner.bundle.resourcePath;
#define MACOSX_ROMDIR @"ROM"
if ([_viceCore isEqualToString:@"Vic20"]){
NSArray<NSString *> *pathList = @[
[NSString pathWithComponents:@[bootPath, MACOSX_ROMDIR, @"VIC20"]],
[NSString pathWithComponents:@[bootPath, MACOSX_ROMDIR, @"DRIVES"]],
];
[_vic20 initializeWithBootPath:bootPath systemPathList:pathList];
_vic20.model = Vic20ModelPAL;
_frameInterval = _vic20.videoFrequency;
} else if([_viceCore isEqualToString:@"C128"]){
NSArray<NSString *> *pathList = @[
[NSString pathWithComponents:@[bootPath, MACOSX_ROMDIR, @"C128"]],
[NSString pathWithComponents:@[bootPath, MACOSX_ROMDIR, @"DRIVES"]],
];
[_c128 initializeWithBootPath:bootPath systemPathList:pathList];
_c128.model = C128ModelPAL;
_frameInterval = _c128.videoFrequency;
}else if ([_viceCore isEqualToString:@"DTV"]){
NSArray<NSString *> *pathList = @[
[NSString pathWithComponents:@[bootPath, MACOSX_ROMDIR, @"C64DTV"]],
[NSString pathWithComponents:@[bootPath, MACOSX_ROMDIR, @"DRIVES"]],
];
[_c64dtv initializeWithBootPath:bootPath systemPathList:pathList];
_c64dtv.model = DTVModelV3PAL;
_frameInterval = _c64dtv.videoFrequency;
} else {
NSArray<NSString *> *pathList = @[
[NSString pathWithComponents:@[bootPath, MACOSX_ROMDIR, @"C64"]],
[NSString pathWithComponents:@[bootPath, MACOSX_ROMDIR, @"DRIVES"]],
];
[_c64 initializeWithBootPath:bootPath systemPathList:pathList];
_c64.model = C64ModelPAL;
_frameInterval = _c64.videoFrequency;
}
// Use the last selected display mode or default to the appropriate for the user system locale
if (self.displayModeInfo == nil)
{
if ([self.systemRegion isEqualToString: @"North America"] || [self.systemRegion isEqualToString: @"Japan"])
[self changeDisplayWithMode:@"NTSC"];
else
[self changeDisplayWithMode:@"PAL"];
}
else
{
NSString *lastSelectedModeToSet = self.displayModeInfo[@"format"];
[self changeDisplayWithMode:lastSelectedModeToSet];
}
initialized = true;
}
}
@end