535 lines
15 KiB
Plaintext
Executable File
535 lines
15 KiB
Plaintext
Executable File
/*
|
|
Copyright (c) 2015, 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 "VC64GameCore.h"
|
|
#import "C64.h"
|
|
#import "C64Proxy+Private.h"
|
|
#import "OEC64SystemResponderClient.h"
|
|
#import "VirtualC64-Swift.h"
|
|
|
|
#import <OpenGL/gl.h>
|
|
#import <Carbon/Carbon.h>
|
|
#import <OpenEmuBase/OERingBuffer.h>
|
|
|
|
#define SOUNDBUFFERSIZE 2048
|
|
|
|
@interface VC64GameCore () <OEC64SystemResponderClient>
|
|
{
|
|
C64 *c64;
|
|
C64Proxy *_proxy;
|
|
KeyboardController *_kbd;
|
|
BOOL _isJoystickPortSwapped;
|
|
NSString *_fileToLoad;
|
|
float *_soundBuffer;
|
|
uint32_t *_videoBuffer;
|
|
BOOL _didRUN;
|
|
|
|
// Used to tell the system that the C64 has finished loading and is ready for interaction
|
|
BOOL isC64Ready;
|
|
|
|
// Used to control if we are still waiting for the prompt, or if we are finally there
|
|
BOOL isAtReadyPrompt;
|
|
BOOL waitingForReady;
|
|
|
|
// Used to make sure we don't send more text if it is in the process of auto typing already
|
|
BOOL isStillTyping;
|
|
|
|
//Controls weather we have loaded the game or still in the process of doing so
|
|
BOOL isGameLoading;
|
|
BOOL isGameLoaded;
|
|
}
|
|
|
|
- (void)typeText:(NSString *)text;
|
|
- (void)typeText:(NSString *)text withDelay:(int)delay;
|
|
- (void)checkForReady;
|
|
- (BOOL)loadBIOSRoms;
|
|
@end
|
|
|
|
@implementation VC64GameCore
|
|
|
|
- (id)init
|
|
{
|
|
if((self = [super init]))
|
|
{
|
|
c64 = new C64();
|
|
_proxy = [[C64Proxy alloc] initWithC64:c64];
|
|
_kbd = [[KeyboardController alloc] initWithC64:_proxy];
|
|
|
|
_soundBuffer = (float *)calloc(SOUNDBUFFERSIZE, sizeof(*_soundBuffer));
|
|
|
|
isC64Ready = false;
|
|
isAtReadyPrompt = false;
|
|
waitingForReady = false;
|
|
isStillTyping = false;
|
|
isGameLoading = false;
|
|
isGameLoaded = false;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
free(_soundBuffer);
|
|
}
|
|
|
|
#pragma mark - Execution
|
|
|
|
- (BOOL)loadFileAtPath:(NSString *)path
|
|
{
|
|
_fileToLoad = [path copy];
|
|
|
|
// System
|
|
// TODO: Determine region
|
|
c64->vic.setModel(NTSC_6567);
|
|
|
|
if(![self loadBIOSRoms])
|
|
return NO;
|
|
|
|
// Peripherals
|
|
c64->setAlwaysWarp(false);
|
|
// c64->setWarp(false);
|
|
c64->setWarpLoad(false); // Leave disabled otherwise audio can get slightly out of sync
|
|
c64->drive1.setSendSoundMessages(false);
|
|
// c64->drive1.setBitAccuracy(true); // Disable to put drive in a faster, but less compatible read-only mode
|
|
|
|
// Audio
|
|
c64->sid.setReSID(true);
|
|
c64->sid.setModel(MOS_6581); // MOS6581 or MOS8580
|
|
c64->sid.setSamplingMethod(SID_SAMPLE_FAST);
|
|
c64->sid.setAudioFilter(false);
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)setupEmulation
|
|
{
|
|
// Power on sub components
|
|
c64->sid.run();
|
|
c64->cpu.clearErrorState();
|
|
c64->drive1.cpu.clearErrorState();
|
|
c64->drive2.cpu.clearErrorState();
|
|
c64->restartTimer();
|
|
}
|
|
|
|
- (void)executeFrame
|
|
{
|
|
// Run the game loop ourselves
|
|
int samples = c64->sid.getSampleRate() / c64->vic.getFramesPerSecond();
|
|
|
|
c64->executeOneFrame();
|
|
|
|
// copy video buffer
|
|
memcpy(_videoBuffer, c64->vic.screenBuffer(), self.bufferSize.width * self.bufferSize.height * sizeof(uint32_t));
|
|
|
|
if(_didRUN)
|
|
{
|
|
c64->sid.readMonoSamples(_soundBuffer, samples);
|
|
[[self audioBufferAtIndex:0] write:_soundBuffer maxLength:samples * sizeof(*_soundBuffer)];
|
|
}
|
|
else
|
|
{
|
|
if (!isC64Ready)
|
|
{
|
|
[self checkForReady]; //this is called every Execute frame C64 if not at ready prompt.
|
|
}
|
|
else
|
|
{
|
|
if(!isGameLoaded && !isGameLoading) //If there is not game loaded, and we are not in the process of loading one
|
|
{
|
|
//start the load procedure
|
|
NSString *fileExtension = [[_fileToLoad pathExtension] lowercaseString];
|
|
|
|
[self _loadGame:fileExtension ];
|
|
}
|
|
else
|
|
{
|
|
if(isGameLoaded && !_didRUN && !isStillTyping)
|
|
{
|
|
if (!waitingForReady )
|
|
{
|
|
isAtReadyPrompt=false;
|
|
waitingForReady=true;
|
|
}
|
|
if (!isAtReadyPrompt)
|
|
{
|
|
[self checkForReady];
|
|
}
|
|
else
|
|
{
|
|
[self typeText:@"run\n" withDelay:0];
|
|
_didRUN=true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)resetEmulation
|
|
{
|
|
c64->cpu.reset();
|
|
isC64Ready=false;
|
|
isAtReadyPrompt=false;
|
|
isGameLoaded=false;
|
|
isGameLoading=false;
|
|
waitingForReady=false;
|
|
_didRUN = NO;
|
|
}
|
|
|
|
- (void)stopEmulation
|
|
{
|
|
c64->halt();
|
|
isC64Ready=false;
|
|
isAtReadyPrompt=false;
|
|
isGameLoaded=false;
|
|
isGameLoading=false;
|
|
waitingForReady=false;
|
|
_didRUN = NO;
|
|
|
|
[super stopEmulation];
|
|
}
|
|
|
|
- (NSTimeInterval)frameInterval
|
|
{
|
|
return c64->vic.getFramesPerSecond();
|
|
}
|
|
|
|
// Doesn't seem to work correctly, audio still goes out of sync
|
|
// Use setWarp instead of Always warp
|
|
-(void)fastForward:(BOOL)flag
|
|
{
|
|
// flag ? c64->setWarp(true) : c64->setWarp(false);
|
|
|
|
[super fastForward:flag];
|
|
}
|
|
|
|
|
|
#pragma mark - Video
|
|
|
|
- (const void*)getVideoBufferWithHint:(void *)hint {
|
|
_videoBuffer = static_cast<uint32_t *>(hint);
|
|
return hint;
|
|
}
|
|
|
|
- (OEIntSize)bufferSize
|
|
{
|
|
return OEIntSizeMake(NTSC_PIXELS, NTSC_RASTERLINES);
|
|
}
|
|
|
|
- (OEIntRect)screenRect
|
|
{
|
|
return OEIntRectMake(0, 0, NTSC_PIXELS, NTSC_RASTERLINES);
|
|
}
|
|
|
|
- (OEIntSize)aspectSize
|
|
{
|
|
return OEIntSizeMake(NTSC_PIXELS * (3.0/4.0), NTSC_RASTERLINES);
|
|
}
|
|
|
|
- (GLenum)pixelFormat
|
|
{
|
|
return GL_RGBA;
|
|
}
|
|
|
|
- (GLenum)pixelType
|
|
{
|
|
return GL_UNSIGNED_INT_8_8_8_8_REV;
|
|
}
|
|
|
|
- (GLenum)internalPixelFormat
|
|
{
|
|
return GL_RGBA;
|
|
}
|
|
|
|
#pragma mark - Audio
|
|
|
|
- (double)audioSampleRate
|
|
{
|
|
return c64->sid.getSampleRate();
|
|
}
|
|
|
|
- (NSUInteger)audioBitDepth
|
|
{
|
|
return 32;
|
|
}
|
|
|
|
- (NSUInteger)channelCount
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
#pragma mark - Save States
|
|
|
|
- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
|
|
{
|
|
c64->takeUserSnapshotSafe();
|
|
auto nr = c64->numUserSnapshots() - 1;
|
|
auto saveState = c64->userSnapshot(nr);
|
|
block(saveState->writeToFile(fileName.fileSystemRepresentation),nil);
|
|
c64->deleteUserSnapshot(nr);
|
|
}
|
|
|
|
- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
|
|
{
|
|
Snapshot *saveState = new Snapshot;
|
|
block(saveState->readFromFile(fileName.fileSystemRepresentation),nil);
|
|
c64->loadFromSnapshotSafe(saveState);
|
|
}
|
|
|
|
#pragma mark - Input
|
|
|
|
- (oneway void)mouseMovedAtPoint:(OEIntPoint)point
|
|
{
|
|
|
|
}
|
|
|
|
- (oneway void)leftMouseDownAtPoint:(OEIntPoint)point
|
|
{
|
|
|
|
}
|
|
|
|
- (oneway void)leftMouseUp
|
|
{
|
|
|
|
}
|
|
|
|
- (oneway void)rightMouseDownAtPoint:(OEIntPoint)point
|
|
{
|
|
|
|
}
|
|
|
|
- (oneway void)rightMouseUp
|
|
{
|
|
|
|
}
|
|
|
|
- (oneway void)keyDown:(unsigned short)keyHIDCode characters:(NSString *)characters charactersIgnoringModifiers:(NSString *)charactersIgnoringModifiers flags:(NSEventModifierFlags)modifierFlags
|
|
{
|
|
// Do not accept input before RUN
|
|
if(!isC64Ready)
|
|
return;
|
|
|
|
[_kbd keyDownWithKeyCode:keyHIDCode characters:charactersIgnoringModifiers flags:modifierFlags];
|
|
}
|
|
|
|
- (oneway void)keyUp:(unsigned short)keyHIDCode characters:(NSString *)characters charactersIgnoringModifiers:(NSString *)charactersIgnoringModifiers flags:(NSEventModifierFlags)modifierFlags
|
|
{
|
|
// Do not accept input before RUN
|
|
if(!isC64Ready)
|
|
return;
|
|
|
|
[_kbd keyUpWithKeyCode:keyHIDCode characters:charactersIgnoringModifiers flags:modifierFlags];
|
|
}
|
|
|
|
- (oneway void)didPushC64Button:(OEC64Button)button forPlayer:(NSUInteger)player;
|
|
{
|
|
ControlPort *port;
|
|
switch (player) {
|
|
case 1:port = _isJoystickPortSwapped ? &c64->port1: &c64->port2;
|
|
case 2:port = _isJoystickPortSwapped ? &c64->port2 : &c64->port1;
|
|
default: return;
|
|
}
|
|
|
|
if(button == OEC64JoystickUp) { port->trigger(PULL_UP); }
|
|
if(button == OEC64JoystickDown) { port->trigger(PULL_DOWN); }
|
|
if(button == OEC64JoystickLeft) { port->trigger(PULL_LEFT); }
|
|
if(button == OEC64JoystickRight) { port->trigger(PULL_RIGHT); }
|
|
if(button == OEC64ButtonFire) { port->trigger(PRESS_FIRE); }
|
|
|
|
}
|
|
|
|
- (oneway void)didReleaseC64Button:(OEC64Button)button forPlayer:(NSUInteger)player;
|
|
{
|
|
ControlPort *port;
|
|
|
|
switch (player) {
|
|
case 1:port = _isJoystickPortSwapped ? &c64->port1: &c64->port2;
|
|
case 2:port = _isJoystickPortSwapped ? &c64->port2 : &c64->port1;
|
|
default: return;
|
|
}
|
|
|
|
switch (button) {
|
|
case OEC64JoystickUp:
|
|
case OEC64JoystickDown:
|
|
port->trigger(RELEASE_Y);
|
|
break;
|
|
|
|
case OEC64JoystickLeft:
|
|
case OEC64JoystickRight:
|
|
port->trigger(RELEASE_X);
|
|
break;
|
|
|
|
case OEC64ButtonFire:
|
|
port->trigger(RELEASE_FIRE);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (oneway void)swapJoysticks {
|
|
// do port swap
|
|
_isJoystickPortSwapped = !_isJoystickPortSwapped;
|
|
}
|
|
|
|
#pragma mark - Misc & Helpers
|
|
|
|
- (BOOL)loadBIOSRoms
|
|
{
|
|
// Get The 4 BIOS ROMs
|
|
|
|
// BASIC ROM
|
|
NSString *basicROM = [[self biosDirectoryPath] stringByAppendingPathComponent:@"basic.901226-01.bin"];
|
|
if (!ROMFile::isBasicRomFile(basicROM.UTF8String))
|
|
{
|
|
NSLog(@"VirtualC64: %@ is not a valid Basic ROM!", basicROM);
|
|
return NO;
|
|
}
|
|
|
|
// "Kernal" ROM
|
|
NSString *kernelROM = [[self biosDirectoryPath] stringByAppendingPathComponent:@"JiffyDOS_C64.bin"];
|
|
if (!ROMFile::isKernalRomFile(kernelROM.UTF8String) || TAPFile::isTAPFile(_fileToLoad.UTF8String))
|
|
{
|
|
kernelROM = [[self biosDirectoryPath] stringByAppendingPathComponent:@"kernal.901227-03.bin"];
|
|
if (!ROMFile::isKernalRomFile(kernelROM.UTF8String))
|
|
{
|
|
NSLog(@"VirtualC64: %@ is not a valid Kernal ROM!", kernelROM);
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
// Char ROM
|
|
NSString *charROM = [[self biosDirectoryPath] stringByAppendingPathComponent:@"characters.901225-01.bin"];
|
|
if (!ROMFile::isCharRomFile(charROM.UTF8String))
|
|
{
|
|
NSLog(@"VirtualC64: %@ is not a valid Char ROM!", charROM);
|
|
return NO;
|
|
}
|
|
|
|
// C1541 aka Floppy ROM
|
|
NSString *C1541ROM = [[self biosDirectoryPath] stringByAppendingPathComponent:@"JiffyDOS_C1541.bin"];
|
|
if (!ROMFile::isVC1541RomFile(C1541ROM.UTF8String))
|
|
{
|
|
C1541ROM = [[self biosDirectoryPath] stringByAppendingPathComponent:@"1541-II.355640-01.bin"];
|
|
if (!ROMFile::isVC1541RomFile(C1541ROM.UTF8String))
|
|
{
|
|
NSLog(@"VirtualC64: %@ is not a valid C1541 ROM!", charROM);
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
// Load Basic, Kernel, Char and C1541 Floppy ROMs
|
|
c64->loadRom([basicROM UTF8String]);
|
|
c64->loadRom([kernelROM UTF8String]);
|
|
c64->loadRom([charROM UTF8String]);
|
|
c64->loadRom([C1541ROM UTF8String]);
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)typeText:(NSString *)text
|
|
{
|
|
[self typeText:text withDelay:0];
|
|
}
|
|
|
|
- (void)typeText:(NSString *)text withDelay:(int)delay
|
|
{
|
|
while (isStillTyping)
|
|
usleep(50);
|
|
|
|
isStillTyping = YES;
|
|
[_kbd typeWithString:text initialDelay:delay completion:^{
|
|
self->isStillTyping = NO;
|
|
}];
|
|
}
|
|
|
|
- (void) _loadGame:(NSString *)fileExtension
|
|
{
|
|
isGameLoading = true;
|
|
|
|
if (CRTFile::isCRTFile(_fileToLoad.UTF8String)) {
|
|
//Cartridge Loading
|
|
_didRUN = true;
|
|
c64->expansionport.attachCartridgeAndReset( CRTFile::makeWithFile(_fileToLoad.UTF8String));
|
|
|
|
}else if (TAPFile::isTAPFile(_fileToLoad.UTF8String)) {
|
|
// Tape Loading
|
|
c64->datasette.insertTape(TAPFile::makeWithFile(_fileToLoad.UTF8String));
|
|
|
|
isStillTyping = YES;
|
|
[_kbd typeWithString:@"LOAD\n" initialDelay:0 completion:^{
|
|
self->isStillTyping = NO;
|
|
c64->datasette.pressPlay();
|
|
}];
|
|
} else {
|
|
//Disk Image/Archive Loading
|
|
auto archive = AnyArchive::makeWithFile(_fileToLoad.UTF8String);
|
|
if (archive != nullptr) {
|
|
c64->drive1.prepareToInsert();
|
|
c64->drive1.insertDisk(archive);
|
|
[self typeText:@"load \"*\",8,1\n" withDelay:500];
|
|
} else {
|
|
[self typeText:@"This is an unknow image file. C64 cannot load it." withDelay:500];
|
|
}
|
|
}
|
|
|
|
isGameLoading = false;
|
|
isGameLoaded = true;
|
|
}
|
|
|
|
- (void) checkForReady
|
|
{
|
|
int pnt = (c64->mem.peek(0x00d1) | (c64->mem.peek(0x00d2) << 8)); //Get Current Cursor position
|
|
int pntr = c64->mem.peek(0x00d3); // Current column on the line
|
|
int lnmx = c64->mem.peek(0x00d5) + 1; // Get the line lenght
|
|
int blnsw = c64->mem.peek(0x00cc); // is the curson blinking? 0 is yes, 1 in no
|
|
int addrStrt = pnt - lnmx; // set the start position in Ram to start looking at the previous line
|
|
char const *s = "READY."; // We are looking for READY.
|
|
bool charsFound = false;
|
|
|
|
for (int i = 0; s[i] != '\0'; i++)
|
|
{
|
|
if (c64->mem.peek(addrStrt + i) == (s[i] % 64))
|
|
{
|
|
charsFound = true;
|
|
}
|
|
else
|
|
{
|
|
charsFound=false;
|
|
}
|
|
}
|
|
|
|
if (charsFound)
|
|
{
|
|
isAtReadyPrompt = true;
|
|
isC64Ready = true;
|
|
NSLog(@"Screen address: %d,%d, %d, %d", pnt,pntr, blnsw, lnmx);
|
|
}
|
|
}
|
|
@end
|