Files
Play-Core/PureiGameCore.mm
C.W. Betts 5fac16b005 Update to the most recent version of Play!
Also bump the deployment target to 10.15.
2024-03-18 16:47:58 -06:00

508 lines
13 KiB
Plaintext

//
// PureiGameCore.m
// Play!
//
// Created by Alexander Strange on 10/24/15.
//
//
#import "PureiGameCore.h"
#import "PS2VM.h"
#import "gs/GSH_OpenGL/GSH_OpenGL.h"
#import "PadHandler.h"
#import "SoundHandler.h"
#import "PS2VM_Preferences.h"
#import "AppConfig.h"
#import "StdStream.h"
#import <OpenEmuBase/OERingBuffer.h>
#import <OpenEmuBase/OETimingUtils.h>
__weak PureiGameCore *_current;
class CGSH_OpenEmu : public CGSH_OpenGL
{
public:
static FactoryFunction GetFactoryFunction();
virtual void InitializeImpl();
protected:
virtual void PresentBackbuffer();
};
class CSH_OpenEmu : public CSoundHandler
{
public:
CSH_OpenEmu() {};
virtual ~CSH_OpenEmu() {};
virtual void Reset();
virtual void Write(int16*, unsigned int, unsigned int);
virtual bool HasFreeBuffers();
virtual void RecycleBuffers();
static FactoryFunction GetFactoryFunction();
};
class CPH_OpenEmu : public CPadHandler
{
public:
CPH_OpenEmu() {};
virtual ~CPH_OpenEmu() {};
void Update(uint8*);
static FactoryFunction GetFactoryFunction();
};
class CBinding
{
public:
virtual ~CBinding() {}
virtual void ProcessEvent(OEPS2Button, uint32) = 0;
virtual uint32 GetValue() const = 0;
};
typedef std::shared_ptr<CBinding> BindingPtr;
class CSimpleBinding : public CBinding
{
public:
CSimpleBinding(OEPS2Button);
virtual ~CSimpleBinding();
virtual void ProcessEvent(OEPS2Button, uint32);
virtual uint32 GetValue() const;
private:
OEPS2Button m_keyCode;
uint32 m_state;
};
class CSimulatedAxisBinding : public CBinding
{
public:
CSimulatedAxisBinding(OEPS2Button, OEPS2Button);
virtual ~CSimulatedAxisBinding();
virtual void ProcessEvent(OEPS2Button, uint32);
virtual uint32 GetValue() const;
private:
OEPS2Button m_negativeKeyCode;
OEPS2Button m_positiveKeyCode;
uint32 m_negativeState;
uint32 m_positiveState;
};
@interface PureiGameCore() <OEPS2SystemResponderClient>
@end
@implementation PureiGameCore
{
@public
// ivars
CPS2VM _ps2VM;
NSString *_romPath;
BindingPtr _bindings[PS2::CControllerInfo::MAX_BUTTONS];
}
- (void)dealloc
{
_current = nil;
}
- (BOOL)loadFileAtPath:(NSString *)path error:(NSError **)error
{
_romPath = [path copy];
return YES;
}
- (void)setupEmulation
{
_current = self;
CAppConfig::GetInstance().SetPreferencePath(PREF_PS2_CDROM0_PATH, [_romPath fileSystemRepresentation]);
NSFileManager *fm = [NSFileManager defaultManager];
NSString *mcd0 = [self.batterySavesDirectoryPath stringByAppendingPathComponent:@"mcd0"];
NSString *mcd1 = [self.batterySavesDirectoryPath stringByAppendingPathComponent:@"mcd1"];
NSString *hdd = [self.batterySavesDirectoryPath stringByAppendingPathComponent:@"hdd"];
if (![fm fileExistsAtPath:mcd0]) {
[fm createDirectoryAtPath:mcd0 withIntermediateDirectories:YES attributes:nil error:NULL];
}
if (![fm fileExistsAtPath:mcd1]) {
[fm createDirectoryAtPath:mcd1 withIntermediateDirectories:YES attributes:nil error:NULL];
}
if (![fm fileExistsAtPath:hdd]) {
[fm createDirectoryAtPath:hdd withIntermediateDirectories:YES attributes:nil error:NULL];
}
CAppConfig::GetInstance().SetPreferencePath(PREF_PS2_MC0_DIRECTORY, mcd0.fileSystemRepresentation);
CAppConfig::GetInstance().SetPreferencePath(PREF_PS2_MC1_DIRECTORY, mcd1.fileSystemRepresentation);
CAppConfig::GetInstance().SetPreferencePath(PREF_PS2_HOST_DIRECTORY, hdd.fileSystemRepresentation);
CAppConfig::GetInstance().SetPreferencePath(PREF_PS2_ROM0_DIRECTORY, self.biosDirectoryPath.fileSystemRepresentation);
CAppConfig::GetInstance().SetPreferenceInteger(PREF_CGSHANDLER_PRESENTATION_MODE, CGSHandler::PRESENTATION_MODE_FIT);
CAppConfig::GetInstance().Save();
_ps2VM.Initialize();
_bindings[PS2::CControllerInfo::START] = std::make_shared<CSimpleBinding>(OEPS2ButtonStart);
_bindings[PS2::CControllerInfo::SELECT] = std::make_shared<CSimpleBinding>(OEPS2ButtonSelect);
_bindings[PS2::CControllerInfo::DPAD_LEFT] = std::make_shared<CSimpleBinding>(OEPS2ButtonLeft);
_bindings[PS2::CControllerInfo::DPAD_RIGHT] = std::make_shared<CSimpleBinding>(OEPS2ButtonRight);
_bindings[PS2::CControllerInfo::DPAD_UP] = std::make_shared<CSimpleBinding>(OEPS2ButtonUp);
_bindings[PS2::CControllerInfo::DPAD_DOWN] = std::make_shared<CSimpleBinding>(OEPS2ButtonDown);
_bindings[PS2::CControllerInfo::SQUARE] = std::make_shared<CSimpleBinding>(OEPS2ButtonSquare);
_bindings[PS2::CControllerInfo::CROSS] = std::make_shared<CSimpleBinding>(OEPS2ButtonCross);
_bindings[PS2::CControllerInfo::TRIANGLE] = std::make_shared<CSimpleBinding>(OEPS2ButtonTriangle);
_bindings[PS2::CControllerInfo::CIRCLE] = std::make_shared<CSimpleBinding>(OEPS2ButtonCircle);
_bindings[PS2::CControllerInfo::L1] = std::make_shared<CSimpleBinding>(OEPS2ButtonL1);
_bindings[PS2::CControllerInfo::L2] = std::make_shared<CSimpleBinding>(OEPS2ButtonL2);
_bindings[PS2::CControllerInfo::L3] = std::make_shared<CSimpleBinding>(OEPS2ButtonL3);
_bindings[PS2::CControllerInfo::R1] = std::make_shared<CSimpleBinding>(OEPS2ButtonR1);
_bindings[PS2::CControllerInfo::R2] = std::make_shared<CSimpleBinding>(OEPS2ButtonR2);
_bindings[PS2::CControllerInfo::R3] = std::make_shared<CSimpleBinding>(OEPS2ButtonR3);
_bindings[PS2::CControllerInfo::ANALOG_LEFT_X] = std::make_shared<CSimulatedAxisBinding>(OEPS2LeftAnalogLeft,OEPS2LeftAnalogRight);
_bindings[PS2::CControllerInfo::ANALOG_LEFT_Y] = std::make_shared<CSimulatedAxisBinding>(OEPS2LeftAnalogUp,OEPS2LeftAnalogDown);
_bindings[PS2::CControllerInfo::ANALOG_RIGHT_X] = std::make_shared<CSimulatedAxisBinding>(OEPS2RightAnalogLeft,OEPS2RightAnalogRight);
_bindings[PS2::CControllerInfo::ANALOG_RIGHT_Y] = std::make_shared<CSimulatedAxisBinding>(OEPS2RightAnalogUp,OEPS2RightAnalogDown);
// TODO: In Debug disable dynarec?
}
- (void)setPauseEmulation:(BOOL)pauseEmulation
{
if (pauseEmulation) {
_ps2VM.Pause();
} else {
_ps2VM.Resume();
}
[super setPauseEmulation:pauseEmulation];
}
- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
{
const fs::path fsName(fileName.fileSystemRepresentation);
auto success = _ps2VM.SaveState(fsName);
success.wait();
block(success.get(), nil);
}
- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
{
//FIXME: load save state at launch fails.
const fs::path fsName(fileName.fileSystemRepresentation);
auto success = _ps2VM.LoadState(fsName);
success.wait();
block(success.get(), nil);
}
- (void)startEmulation
{
_ps2VM.CreateGSHandler(CGSH_OpenEmu::GetFactoryFunction());
_ps2VM.CreatePadHandler(CPH_OpenEmu::GetFactoryFunction());
_ps2VM.CreateSoundHandler(CSH_OpenEmu::GetFactoryFunction());
CGSHandler::PRESENTATION_PARAMS presentationParams;
auto presentationMode = static_cast<CGSHandler::PRESENTATION_MODE>(CAppConfig::GetInstance().GetPreferenceInteger(PREF_CGSHANDLER_PRESENTATION_MODE));
presentationParams.windowWidth = 640;
presentationParams.windowHeight = 480;
presentationParams.mode = presentationMode;
_ps2VM.m_ee->m_gs->SetPresentationParams(presentationParams);
CPS2OS* os = _ps2VM.m_ee->m_os;
os->BootFromCDROM();
// TODO: Play! starts a bunch of threads. They all need to be realtime.
_ps2VM.Resume();
[super startEmulation];
}
- (void)resetEmulation
{
_ps2VM.Pause();
_ps2VM.Reset();
_ps2VM.Resume();
}
-(void)stopEmulation
{
_ps2VM.Pause();
_ps2VM.Destroy();
}
- (void)executeFrame
{
// Do nothing.
}
- (OEGameCoreRendering)gameCoreRendering
{
return OEGameCoreRenderingOpenGL3;
}
- (NSTimeInterval)frameInterval
{
return 60;
}
- (OEIntSize)bufferSize
{
return OEIntSizeMake(640, 480);
}
- (OEIntSize)aspectSize
{
return OEIntSizeMake(4,3);
}
- (BOOL)hasAlternateRenderingThread
{
return YES;
}
- (NSUInteger)channelCount
{
return 2;
}
- (double)audioSampleRate
{
return 44100; // TODO: is this right?
}
- (NSUInteger)audioBufferSizeForBuffer:(NSUInteger)buffer
{
return 2*[super audioBufferSizeForBuffer:buffer];
}
- (oneway void)didMovePS2JoystickDirection:(OEPS2Button)button withValue:(CGFloat)value forPlayer:(NSUInteger)player
{
// FIXME: Player 2 is not yet supported.
if (player != 1) {
return;
}
//TODO: find real scale value
uint32 val = value * 0x7f;
for(auto bindingIterator(std::begin(_bindings));
bindingIterator != std::end(_bindings); bindingIterator++)
{
const auto& binding = (*bindingIterator);
if(!binding) continue;
binding->ProcessEvent(button, val);
}
}
- (oneway void)didPushPS2Button:(OEPS2Button)button forPlayer:(NSUInteger)player
{
// FIXME: Player 2 is not yet supported.
if (player != 1) {
return;
}
for(auto bindingIterator(std::begin(_bindings));
bindingIterator != std::end(_bindings); bindingIterator++)
{
const auto& binding = (*bindingIterator);
if(!binding) continue;
binding->ProcessEvent(button, 1);
}
}
- (oneway void)didReleasePS2Button:(OEPS2Button)button forPlayer:(NSUInteger)player
{
// FIXME: Player 2 is not yet supported.
if (player != 1) {
return;
}
for(auto bindingIterator(std::begin(_bindings));
bindingIterator != std::end(_bindings); bindingIterator++)
{
const auto& binding = (*bindingIterator);
if(!binding) continue;
binding->ProcessEvent(button, 0);
}
}
@end
#pragma mark - Graphics callbacks
static CGSHandler *GSHandlerFactory()
{
return new CGSH_OpenEmu();
}
CGSHandler::FactoryFunction CGSH_OpenEmu::GetFactoryFunction()
{
return GSHandlerFactory;
}
void CGSH_OpenEmu::InitializeImpl()
{
GET_CURRENT_OR_RETURN();
[current.renderDelegate willRenderFrameOnAlternateThread];
CGSH_OpenGL::InitializeImpl();
this->m_presentFramebuffer = [current.renderDelegate.presentationFramebuffer intValue];
glClearColor(0,0,0,0);
glClear(GL_COLOR_BUFFER_BIT);
}
void CGSH_OpenEmu::PresentBackbuffer()
{
GET_CURRENT_OR_RETURN();
[current.renderDelegate didRenderFrameOnAlternateThread];
// Start the next one.
[current.renderDelegate willRenderFrameOnAlternateThread];
}
#pragma mark - Sound callbacks
void CSH_OpenEmu::Reset()
{
}
bool CSH_OpenEmu::HasFreeBuffers()
{
return true;
}
void CSH_OpenEmu::RecycleBuffers()
{
}
void CSH_OpenEmu::Write(int16 *audio, unsigned int sampleCount, unsigned int sampleRate)
{
GET_CURRENT_OR_RETURN();
OERingBuffer *rb = [current audioBufferAtIndex:0];
[rb write:audio maxLength:sampleCount*2];
}
static CSoundHandler *SoundHandlerFactory()
{
OESetThreadRealtime(1. / (1 * 60), .007, .03);
return new CSH_OpenEmu();
}
CSoundHandler::FactoryFunction CSH_OpenEmu::GetFactoryFunction()
{
return SoundHandlerFactory;
}
#pragma mark - Pad callbacks
void CPH_OpenEmu::Update(uint8* ram)
{
GET_CURRENT_OR_RETURN();
for(auto listenerIterator(std::begin(m_interfaces));
listenerIterator != std::end(m_interfaces); listenerIterator++)
{
auto* listener(*listenerIterator);
for(unsigned int i = 0; i < PS2::CControllerInfo::MAX_BUTTONS; i++)
{
const auto& binding = current->_bindings[i];
if(!binding) continue;
uint32 value = binding->GetValue();
auto currentButtonId = static_cast<PS2::CControllerInfo::BUTTON>(i);
if(PS2::CControllerInfo::IsAxis(currentButtonId))
{
listener->SetAxisState(0, currentButtonId, value & 0xFF, ram);
}
else
{
listener->SetButtonState(0, currentButtonId, value != 0, ram);
}
}
}
}
static CPadHandler *PadHandlerFactory()
{
return new CPH_OpenEmu();
}
CPadHandler::FactoryFunction CPH_OpenEmu::GetFactoryFunction()
{
return PadHandlerFactory;
}
#pragma mark -
CSimpleBinding::CSimpleBinding(OEPS2Button keyCode)
: m_keyCode(keyCode)
, m_state(0)
{
}
CSimpleBinding::~CSimpleBinding() = default;
void CSimpleBinding::ProcessEvent(OEPS2Button keyCode, uint32 state)
{
if(keyCode != m_keyCode) return;
m_state = state;
}
uint32 CSimpleBinding::GetValue() const
{
return m_state;
}
#pragma mark -
CSimulatedAxisBinding::CSimulatedAxisBinding(OEPS2Button negativeKeyCode, OEPS2Button positiveKeyCode)
: m_negativeKeyCode(negativeKeyCode)
, m_positiveKeyCode(positiveKeyCode)
, m_negativeState(0)
, m_positiveState(0)
{
}
CSimulatedAxisBinding::~CSimulatedAxisBinding() = default;
void CSimulatedAxisBinding::ProcessEvent(OEPS2Button keyCode, uint32 state)
{
if(keyCode == m_negativeKeyCode)
{
m_negativeState = state;
}
if(keyCode == m_positiveKeyCode)
{
m_positiveState = state;
}
}
uint32 CSimulatedAxisBinding::GetValue() const
{
uint32 value = 0x7F;
if(m_negativeState)
{
value -= m_negativeState;
} else
if(m_positiveState)
{
value += m_positiveState;
}
return value;
}