649 lines
21 KiB
Plaintext
649 lines
21 KiB
Plaintext
/*
|
|
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 "GBGameCore.h"
|
|
#import <OpenEmuBase/OERingBuffer.h>
|
|
#import "OEGBSystemResponderClient.h"
|
|
#import <OpenGL/gl.h>
|
|
|
|
#include <sstream>
|
|
#include "gambatte.h"
|
|
#include "gbcpalettes.h"
|
|
#include "resamplerinfo.h"
|
|
#include "resampler.h"
|
|
|
|
#define OptionDefault(_NAME_, _PREFKEY_) @{ OEGameCoreDisplayModeNameKey : _NAME_, OEGameCoreDisplayModePrefKeyNameKey : _PREFKEY_, OEGameCoreDisplayModeStateKey : @YES, }
|
|
#define Option(_NAME_, _PREFKEY_) @{ OEGameCoreDisplayModeNameKey : _NAME_, OEGameCoreDisplayModePrefKeyNameKey : _PREFKEY_, OEGameCoreDisplayModeStateKey : @NO, }
|
|
#define OptionIndented(_NAME_, _PREFKEY_) @{ OEGameCoreDisplayModeNameKey : _NAME_, OEGameCoreDisplayModePrefKeyNameKey : _PREFKEY_, OEGameCoreDisplayModeStateKey : @NO, OEGameCoreDisplayModeIndentationLevelKey : @(1), }
|
|
#define OptionToggleable(_NAME_, _PREFKEY_) @{ OEGameCoreDisplayModeNameKey : _NAME_, OEGameCoreDisplayModePrefKeyNameKey : _PREFKEY_, OEGameCoreDisplayModeStateKey : @NO, OEGameCoreDisplayModeAllowsToggleKey : @YES, }
|
|
#define OptionToggleableNoSave(_NAME_, _PREFKEY_) @{ OEGameCoreDisplayModeNameKey : _NAME_, OEGameCoreDisplayModePrefKeyNameKey : _PREFKEY_, OEGameCoreDisplayModeStateKey : @NO, OEGameCoreDisplayModeAllowsToggleKey : @YES, OEGameCoreDisplayModeDisallowPrefSaveKey : @YES, }
|
|
#define Label(_NAME_) @{ OEGameCoreDisplayModeLabelKey : _NAME_, }
|
|
#define SeparatorItem() @{ OEGameCoreDisplayModeSeparatorItemKey : @"",}
|
|
|
|
gambatte::GB gb;
|
|
Resampler *resampler;
|
|
uint32_t pad[OEGBButtonCount];
|
|
|
|
class GetInput : public gambatte::InputGetter
|
|
{
|
|
public:
|
|
unsigned operator()()
|
|
{
|
|
return pad[0];
|
|
}
|
|
} static GetInput;
|
|
|
|
@interface GBGameCore () <OEGBSystemResponderClient>
|
|
{
|
|
uint32_t *_videoBuffer;
|
|
uint32_t *_inSoundBuffer;
|
|
int16_t *_outSoundBuffer;
|
|
double _sampleRate;
|
|
NSMutableDictionary <NSString *, NSNumber *> *_cheatList;
|
|
NSMutableArray <NSMutableDictionary <NSString *, id> *> *_availableDisplayModes;
|
|
}
|
|
|
|
- (void)applyCheat:(NSString *)code;
|
|
|
|
- (void)loadDisplayModeOptions;
|
|
- (NSString *)gameInternalName;
|
|
- (BOOL)gameHasInternalPalette;
|
|
- (void)loadPalette;
|
|
- (void)loadPaletteDefault;
|
|
- (void)changePalette:(NSString *)palette;
|
|
|
|
@end
|
|
|
|
@implementation GBGameCore
|
|
|
|
- (id)init
|
|
{
|
|
if((self = [super init]))
|
|
{
|
|
_inSoundBuffer = (uint32_t *)malloc(2064 * 2 * 4);
|
|
_outSoundBuffer = (int16_t *)malloc(2064 * 2 * 2);
|
|
_cheatList = [NSMutableDictionary dictionary];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
free(_videoBuffer);
|
|
free(_inSoundBuffer);
|
|
free(_outSoundBuffer);
|
|
}
|
|
|
|
# pragma mark - Execution
|
|
|
|
- (BOOL)loadFileAtPath:(NSString *)path error:(NSError **)error
|
|
{
|
|
memset(pad, 0, sizeof(pad));
|
|
|
|
// Set battery save dir
|
|
NSURL *batterySavesDirectory = [NSURL fileURLWithPath:self.batterySavesDirectoryPath];
|
|
[[NSFileManager defaultManager] createDirectoryAtURL:batterySavesDirectory withIntermediateDirectories:YES attributes:nil error:nil];
|
|
gb.setSaveDir(batterySavesDirectory.fileSystemRepresentation);
|
|
|
|
// Set input state callback
|
|
gb.setInputGetter(&GetInput);
|
|
|
|
// Setup resampler
|
|
double fps = 4194304.0 / 70224.0;
|
|
double inSampleRate = fps * 35112; // 2097152
|
|
|
|
// 2 = "Very high quality (polyphase FIR)", see resamplerinfo.cpp
|
|
resampler = ResamplerInfo::get(2).create(inSampleRate, 48000.0, 2 * 2064);
|
|
|
|
unsigned long mul, div;
|
|
resampler->exactRatio(mul, div);
|
|
|
|
double outSampleRate = inSampleRate * mul / div;
|
|
_sampleRate = outSampleRate; // 47994.326636
|
|
|
|
if (gb.load(path.fileSystemRepresentation) != 0)
|
|
return NO;
|
|
|
|
[self loadDisplayModeOptions];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)executeFrame
|
|
{
|
|
size_t samples = 2064;
|
|
|
|
while (gb.runFor(_videoBuffer, 160, _inSoundBuffer, samples) == -1) {
|
|
[self outputAudio:samples];
|
|
samples = 2064;
|
|
}
|
|
|
|
[self outputAudio:samples];
|
|
}
|
|
|
|
- (void)resetEmulation
|
|
{
|
|
gb.reset();
|
|
}
|
|
|
|
- (void)stopEmulation
|
|
{
|
|
gb.saveSavedata();
|
|
|
|
delete resampler;
|
|
|
|
[super stopEmulation];
|
|
}
|
|
|
|
- (NSTimeInterval)frameInterval
|
|
{
|
|
return 59.727501;
|
|
}
|
|
|
|
# pragma mark - Video
|
|
|
|
- (const void *)getVideoBufferWithHint:(void *)hint
|
|
{
|
|
if (!hint) {
|
|
if (!_videoBuffer) _videoBuffer = (uint32_t *)malloc(160 * 144 * 4);
|
|
hint = _videoBuffer;
|
|
}
|
|
return _videoBuffer = (uint32_t*)hint;
|
|
}
|
|
|
|
- (OEIntRect)screenRect
|
|
{
|
|
return OEIntRectMake(0, 0, 160, 144);
|
|
}
|
|
|
|
- (OEIntSize)bufferSize
|
|
{
|
|
return OEIntSizeMake(160, 144);
|
|
}
|
|
|
|
- (OEIntSize)aspectSize
|
|
{
|
|
return OEIntSizeMake(10, 9);
|
|
}
|
|
|
|
- (GLenum)pixelFormat
|
|
{
|
|
return GL_BGRA;
|
|
}
|
|
|
|
- (GLenum)pixelType
|
|
{
|
|
return GL_UNSIGNED_INT_8_8_8_8_REV;
|
|
}
|
|
|
|
# pragma mark - Audio
|
|
|
|
- (double)audioSampleRate
|
|
{
|
|
return _sampleRate;
|
|
}
|
|
|
|
- (NSUInteger)channelCount
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
# pragma mark - Save States
|
|
|
|
- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
|
|
{
|
|
int success = gb.saveState(0, 0, fileName.fileSystemRepresentation);
|
|
if(block) block(success==1, nil);
|
|
}
|
|
|
|
- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
|
|
{
|
|
int success = gb.loadState(fileName.fileSystemRepresentation);
|
|
if(block) block(success==1, nil);
|
|
}
|
|
|
|
- (NSData *)serializeStateWithError:(NSError **)outError
|
|
{
|
|
std::stringstream stream(std::ios::in|std::ios::out|std::ios::binary);
|
|
|
|
if(gb.serializeState(stream)) {
|
|
stream.seekg(0, std::ios::end);
|
|
NSUInteger length = stream.tellg();
|
|
stream.seekg(0, std::ios::beg);
|
|
|
|
char *bytes = (char *)malloc(length);
|
|
stream.read(bytes, length);
|
|
|
|
return [NSData dataWithBytesNoCopy:bytes length:length];
|
|
}
|
|
|
|
if(outError) {
|
|
*outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotSaveStateError userInfo:@{
|
|
NSLocalizedDescriptionKey : @"Save state data could not be written",
|
|
NSLocalizedRecoverySuggestionErrorKey : @"The emulator could not write the state data."
|
|
}];
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (BOOL)deserializeState:(NSData *)state withError:(NSError **)outError
|
|
{
|
|
std::stringstream stream(std::ios::in|std::ios::out|std::ios::binary);
|
|
|
|
char const *bytes = (char const *)(state.bytes);
|
|
std::streamsize size = state.length;
|
|
stream.write(bytes, size);
|
|
|
|
if(gb.deserializeState(stream))
|
|
return YES;
|
|
|
|
if(outError) {
|
|
*outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:@{
|
|
NSLocalizedDescriptionKey : @"The save state data could not be read",
|
|
NSLocalizedRecoverySuggestionErrorKey : @"Could not load data from the save state"
|
|
}];
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
# pragma mark - Input
|
|
|
|
const int GBMap[] = {gambatte::InputGetter::UP, gambatte::InputGetter::DOWN, gambatte::InputGetter::LEFT, gambatte::InputGetter::RIGHT, gambatte::InputGetter::A, gambatte::InputGetter::B, gambatte::InputGetter::START, gambatte::InputGetter::SELECT};
|
|
- (oneway void)didPushGBButton:(OEGBButton)button;
|
|
{
|
|
pad[0] |= GBMap[button];
|
|
}
|
|
|
|
- (oneway void)didReleaseGBButton:(OEGBButton)button;
|
|
{
|
|
pad[0] &= ~GBMap[button];
|
|
}
|
|
|
|
#pragma mark - Cheats
|
|
|
|
- (void)setCheat:(NSString *)code setType:(NSString *)type setEnabled:(BOOL)enabled
|
|
{
|
|
// Sanitize
|
|
code = [code stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
|
|
|
|
// Gambatte expects cheats UPPERCASE
|
|
code = code.uppercaseString;
|
|
|
|
// Remove any spaces
|
|
code = [code stringByReplacingOccurrencesOfString:@" " withString:@""];
|
|
|
|
if (enabled)
|
|
_cheatList[code] = @YES;
|
|
else
|
|
[_cheatList removeObjectForKey:code];
|
|
|
|
NSMutableArray <NSString *> *combinedGameSharkCodes = [NSMutableArray array];
|
|
NSMutableArray <NSString *> *combinedGameGenieCodes = [NSMutableArray array];
|
|
|
|
// Gambatte expects all cheats in one combined string per-type e.g. 01xxxxxx+01xxxxxx
|
|
// Add enabled per-type cheats to arrays and later join them all by a '+' separator
|
|
for (NSString *key in _cheatList)
|
|
{
|
|
if ([_cheatList[key] boolValue])
|
|
{
|
|
// GameShark
|
|
if (![key containsString:@"-"])
|
|
[combinedGameSharkCodes addObject:key];
|
|
// Game Genie
|
|
else if ([key containsString:@"-"])
|
|
[combinedGameGenieCodes addObject:key];
|
|
}
|
|
}
|
|
|
|
// Apply combined cheats or force a final reset if all cheats are disabled
|
|
[self applyCheat:combinedGameSharkCodes.count != 0 ? [combinedGameSharkCodes componentsJoinedByString:@"+"] : @"0"];
|
|
[self applyCheat:combinedGameGenieCodes.count != 0 ? [combinedGameGenieCodes componentsJoinedByString:@"+"] : @"0-"];
|
|
}
|
|
|
|
# pragma mark - Display Mode
|
|
|
|
- (NSArray <NSDictionary <NSString *, id> *> *)displayModes
|
|
{
|
|
if (_availableDisplayModes.count == 0)
|
|
{
|
|
_availableDisplayModes = [NSMutableArray array];
|
|
|
|
NSArray <NSDictionary <NSString *, id> *> *availableModesWithDefault;
|
|
|
|
if (gb.isCgb())
|
|
{
|
|
availableModesWithDefault =
|
|
@[
|
|
Label(@"Color Correction"),
|
|
OptionDefault(@"Default", @"colorCorrection"),
|
|
Option(@"Modern", @"colorCorrection"),
|
|
];
|
|
}
|
|
else
|
|
{
|
|
availableModesWithDefault =
|
|
@[
|
|
Option(@"Internal", @"palette"),
|
|
Option(@"Grayscale", @"palette"),
|
|
Option(@"Greenscale", @"palette"),
|
|
Option(@"Pocket", @"palette"),
|
|
SeparatorItem(),
|
|
Label(@"GBC Palettes"),
|
|
Option(@"Blue", @"palette"),
|
|
Option(@"Dark Blue", @"palette"),
|
|
Option(@"Green", @"palette"),
|
|
Option(@"Dark Green", @"palette"),
|
|
Option(@"Brown", @"palette"),
|
|
Option(@"Dark Brown", @"palette"),
|
|
Option(@"Red", @"palette"),
|
|
Option(@"Yellow", @"palette"),
|
|
Option(@"Orange", @"palette"),
|
|
Option(@"Pastel Mix", @"palette"),
|
|
Option(@"Inverted", @"palette"),
|
|
];
|
|
}
|
|
|
|
// Deep mutable copy
|
|
_availableDisplayModes = (NSMutableArray *)CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFArrayRef)availableModesWithDefault, kCFPropertyListMutableContainers));
|
|
|
|
if (!gb.isCgb() && ![self gameHasInternalPalette])
|
|
[_availableDisplayModes removeObjectAtIndex:0];
|
|
}
|
|
|
|
return [_availableDisplayModes copy];
|
|
}
|
|
|
|
- (void)changeDisplayWithMode:(NSString *)displayMode
|
|
{
|
|
// NOTE: This is a more complex implementation to serve as an example for handling submenus,
|
|
// toggleable options and multiple groups of mutually exclusive options.
|
|
|
|
if (_availableDisplayModes.count == 0)
|
|
[self displayModes];
|
|
|
|
// First check if 'displayMode' is toggleable and grab its preference key
|
|
BOOL isDisplayModeToggleable = NO;
|
|
BOOL isValidDisplayMode = NO;
|
|
BOOL displayModeState = NO;
|
|
NSString *displayModePrefKey;
|
|
|
|
for (NSDictionary *modeDict in _availableDisplayModes)
|
|
{
|
|
if ([modeDict[OEGameCoreDisplayModeNameKey] isEqualToString:displayMode])
|
|
{
|
|
displayModeState = [modeDict[OEGameCoreDisplayModeStateKey] boolValue];
|
|
displayModePrefKey = modeDict[OEGameCoreDisplayModePrefKeyNameKey];
|
|
isDisplayModeToggleable = [modeDict[OEGameCoreDisplayModeAllowsToggleKey] boolValue];
|
|
isValidDisplayMode = YES;
|
|
break;
|
|
}
|
|
// Submenu Items
|
|
for (NSDictionary *subModeDict in modeDict[OEGameCoreDisplayModeGroupItemsKey])
|
|
{
|
|
if ([subModeDict[OEGameCoreDisplayModeNameKey] isEqualToString:displayMode])
|
|
{
|
|
displayModeState = [subModeDict[OEGameCoreDisplayModeStateKey] boolValue];
|
|
displayModePrefKey = subModeDict[OEGameCoreDisplayModePrefKeyNameKey];
|
|
isDisplayModeToggleable = [subModeDict[OEGameCoreDisplayModeAllowsToggleKey] boolValue];
|
|
isValidDisplayMode = YES;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Disallow a 'displayMode' not found in _availableDisplayModes
|
|
if (!isValidDisplayMode)
|
|
return;
|
|
|
|
// Handle option state changes
|
|
for (NSMutableDictionary *optionDict in _availableDisplayModes)
|
|
{
|
|
NSString *modeName = optionDict[OEGameCoreDisplayModeNameKey];
|
|
NSString *prefKey = optionDict[OEGameCoreDisplayModePrefKeyNameKey];
|
|
|
|
if (!modeName && !optionDict[OEGameCoreDisplayModeGroupNameKey])
|
|
continue;
|
|
// Mutually exclusive option state change
|
|
else if ([modeName isEqualToString:displayMode] && !isDisplayModeToggleable)
|
|
optionDict[OEGameCoreDisplayModeStateKey] = @YES;
|
|
// Reset mutually exclusive options that are the same prefs group as 'displayMode'
|
|
else if (!isDisplayModeToggleable && [prefKey isEqualToString:displayModePrefKey])
|
|
optionDict[OEGameCoreDisplayModeStateKey] = @NO;
|
|
// Toggleable option state change
|
|
else if ([modeName isEqualToString:displayMode] && isDisplayModeToggleable)
|
|
optionDict[OEGameCoreDisplayModeStateKey] = @(!displayModeState);
|
|
// Submenu group
|
|
else if (optionDict[OEGameCoreDisplayModeGroupNameKey])
|
|
{
|
|
// Submenu items
|
|
for (NSMutableDictionary *subOptionDict in optionDict[OEGameCoreDisplayModeGroupItemsKey])
|
|
{
|
|
NSString *modeName = subOptionDict[OEGameCoreDisplayModeNameKey];
|
|
NSString *prefKey = subOptionDict[OEGameCoreDisplayModePrefKeyNameKey];
|
|
|
|
if (!modeName)
|
|
continue;
|
|
// Mutually exclusive option state change
|
|
else if ([modeName isEqualToString:displayMode] && !isDisplayModeToggleable)
|
|
subOptionDict[OEGameCoreDisplayModeStateKey] = @YES;
|
|
// Reset mutually exclusive options that are the same prefs group as 'displayMode'
|
|
else if (!isDisplayModeToggleable && [prefKey isEqualToString:displayModePrefKey])
|
|
subOptionDict[OEGameCoreDisplayModeStateKey] = @NO;
|
|
// Toggleable option state change
|
|
else if ([modeName isEqualToString:displayMode] && isDisplayModeToggleable)
|
|
subOptionDict[OEGameCoreDisplayModeStateKey] = @(!displayModeState);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Set the new palette
|
|
if ([displayModePrefKey isEqualToString:@"palette"])
|
|
[self changePalette:displayMode];
|
|
else if ([displayModePrefKey isEqualToString:@"colorCorrection"])
|
|
gb.setCgbColorCorrection([displayMode isEqual:@"Modern"] ? 1 : 0);
|
|
}
|
|
|
|
# pragma mark - Misc Helper Methods
|
|
|
|
- (void)outputAudio:(size_t)frames
|
|
{
|
|
if (!frames)
|
|
return;
|
|
|
|
size_t len = resampler->resample(_outSoundBuffer, reinterpret_cast<const int16_t *>(_inSoundBuffer), frames);
|
|
|
|
if (len)
|
|
[[self audioBufferAtIndex:0] write:_outSoundBuffer maxLength:len << 2];
|
|
}
|
|
|
|
- (void)applyCheat:(NSString *)code
|
|
{
|
|
std::string s = [code UTF8String];
|
|
if (s.find("-") != std::string::npos)
|
|
gb.setGameGenie(s);
|
|
else
|
|
gb.setGameShark(s);
|
|
}
|
|
|
|
- (void)loadDisplayModeOptions
|
|
{
|
|
if (gb.isCgb())
|
|
{
|
|
// Restore color correction
|
|
NSString *lastColorCorrection = self.displayModeInfo[@"colorCorrection"] ?: @"Default";
|
|
[self changeDisplayWithMode:lastColorCorrection];
|
|
}
|
|
else
|
|
{
|
|
// Load built-in GBC palette for monochrome games if supported
|
|
[self loadPalette];
|
|
}
|
|
}
|
|
|
|
- (NSString *)gameInternalName
|
|
{
|
|
NSString *title = [NSString stringWithUTF8String:gb.romTitle().c_str()];
|
|
return title;
|
|
}
|
|
|
|
- (BOOL)gameHasInternalPalette
|
|
{
|
|
unsigned short *gbc_bios_palette = NULL;
|
|
NSString *title = [self gameInternalName];
|
|
gbc_bios_palette = const_cast<unsigned short *>(findGbcTitlePal(title.UTF8String));
|
|
|
|
return gbc_bios_palette != 0 ? YES : NO;
|
|
}
|
|
|
|
- (void)loadPalette
|
|
{
|
|
// Only temporary, so core doesn't crash on an older OpenEmu version
|
|
if (![self respondsToSelector:@selector(displayModeInfo)])
|
|
{
|
|
[self loadPaletteDefault];
|
|
}
|
|
// No previous palette saved, set a default
|
|
else if (self.displayModeInfo[@"palette"] == nil)
|
|
{
|
|
[self loadPaletteDefault];
|
|
}
|
|
else
|
|
{
|
|
NSString *lastPalette = self.displayModeInfo[@"palette"];
|
|
// Don't try to load "Internal" palette for a game without one
|
|
if ([lastPalette isEqualToString:@"Internal"] && ![self gameHasInternalPalette])
|
|
[self changeDisplayWithMode:@"Grayscale"];
|
|
else
|
|
[self changeDisplayWithMode:lastPalette];
|
|
}
|
|
}
|
|
|
|
- (void)loadPaletteDefault
|
|
{
|
|
if ([self gameHasInternalPalette])
|
|
// load a GBC BIOS builtin palette
|
|
[self changeDisplayWithMode:@"Internal"];
|
|
else
|
|
// no custom palette found, load the default (Original Grayscale)
|
|
[self changeDisplayWithMode:@"Grayscale"];
|
|
}
|
|
|
|
- (void)changePalette:(NSString *)palette
|
|
{
|
|
NSDictionary <NSString *, NSString *> *paletteNames =
|
|
@{
|
|
@"Internal" : @"Internal",
|
|
@"Grayscale" : @"GBC - Grayscale",
|
|
@"Greenscale" : @"Greenscale",
|
|
@"Pocket" : @"Pocket",
|
|
@"Blue" : @"GBC - Blue",
|
|
@"Dark Blue" : @"GBC - Dark Blue",
|
|
@"Green" : @"GBC - Green",
|
|
@"Dark Green" : @"GBC - Dark Green",
|
|
@"Brown" : @"GBC - Brown",
|
|
@"Dark Brown" : @"GBC - Dark Brown",
|
|
@"Red" : @"GBC - Red",
|
|
@"Yellow" : @"GBC - Yellow",
|
|
@"Orange" : @"GBC - Orange",
|
|
@"Pastel Mix" : @"GBC - Pastel Mix",
|
|
@"Inverted" : @"GBC - Inverted",
|
|
};
|
|
|
|
palette = paletteNames[palette];
|
|
unsigned short *gbc_bios_palette = NULL;
|
|
|
|
if ([palette isEqualToString:@"Internal"])
|
|
{
|
|
NSString *title = [self gameInternalName];
|
|
gbc_bios_palette = const_cast<unsigned short *>(findGbcTitlePal(title.UTF8String));
|
|
}
|
|
else if ([palette isEqualToString:@"Greenscale"])
|
|
{
|
|
// GB Pea Soup Green
|
|
gb.setDmgPaletteColor(0, 0, 8369468);
|
|
gb.setDmgPaletteColor(0, 1, 6728764);
|
|
gb.setDmgPaletteColor(0, 2, 3629872);
|
|
gb.setDmgPaletteColor(0, 3, 3223857);
|
|
gb.setDmgPaletteColor(1, 0, 8369468);
|
|
gb.setDmgPaletteColor(1, 1, 6728764);
|
|
gb.setDmgPaletteColor(1, 2, 3629872);
|
|
gb.setDmgPaletteColor(1, 3, 3223857);
|
|
gb.setDmgPaletteColor(2, 0, 8369468);
|
|
gb.setDmgPaletteColor(2, 1, 6728764);
|
|
gb.setDmgPaletteColor(2, 2, 3629872);
|
|
gb.setDmgPaletteColor(2, 3, 3223857);
|
|
return;
|
|
}
|
|
else if ([palette isEqualToString:@"Pocket"])
|
|
{
|
|
// GB Pocket
|
|
gb.setDmgPaletteColor(0, 0, 13487791);
|
|
gb.setDmgPaletteColor(0, 1, 10987158);
|
|
gb.setDmgPaletteColor(0, 2, 6974033);
|
|
gb.setDmgPaletteColor(0, 3, 2828823);
|
|
gb.setDmgPaletteColor(1, 0, 13487791);
|
|
gb.setDmgPaletteColor(1, 1, 10987158);
|
|
gb.setDmgPaletteColor(1, 2, 6974033);
|
|
gb.setDmgPaletteColor(1, 3, 2828823);
|
|
gb.setDmgPaletteColor(2, 0, 13487791);
|
|
gb.setDmgPaletteColor(2, 1, 10987158);
|
|
gb.setDmgPaletteColor(2, 2, 6974033);
|
|
gb.setDmgPaletteColor(2, 3, 2828823);
|
|
|
|
// gb.setDmgPaletteColor(0, 0, 13029285);
|
|
// gb.setDmgPaletteColor(0, 1, 9213547);
|
|
// gb.setDmgPaletteColor(0, 2, 4870457);
|
|
// gb.setDmgPaletteColor(0, 3, 1580056);
|
|
// gb.setDmgPaletteColor(1, 0, 13029285);
|
|
// gb.setDmgPaletteColor(1, 1, 9213547);
|
|
// gb.setDmgPaletteColor(1, 2, 4870457);
|
|
// gb.setDmgPaletteColor(1, 3, 1580056);
|
|
// gb.setDmgPaletteColor(2, 0, 13029285);
|
|
// gb.setDmgPaletteColor(2, 1, 9213547);
|
|
// gb.setDmgPaletteColor(2, 2, 4870457);
|
|
// gb.setDmgPaletteColor(2, 3, 1580056);
|
|
return;
|
|
}
|
|
else
|
|
gbc_bios_palette = const_cast<unsigned short *>(findGbcDirPal(palette.UTF8String));
|
|
|
|
unsigned long rgb32 = 0;
|
|
for (unsigned palnum = 0; palnum < 3; ++palnum)
|
|
{
|
|
for (unsigned colornum = 0; colornum < 4; ++colornum)
|
|
{
|
|
rgb32 = gbcToRgb32(gbc_bios_palette[palnum * 4 + colornum]);
|
|
gb.setDmgPaletteColor(palnum, colornum, rgb32);
|
|
}
|
|
}
|
|
}
|
|
|
|
@end
|