Add Cartridge Instances – allow multiple saves without multiple ROM copies

This commit is contained in:
Lior Halphon 2022-02-26 01:47:47 +02:00
parent 641f26e13e
commit 2c635c7a87
3 changed files with 167 additions and 14 deletions

View File

@ -14,6 +14,30 @@
#import "GBObjectView.h" #import "GBObjectView.h"
#import "GBPaletteView.h" #import "GBPaletteView.h"
@implementation NSString (relativePath)
- (NSString *)pathRelativeToDirectory:(NSString *)directory
{
NSMutableArray<NSString *> *baseComponents = [[directory pathComponents] mutableCopy];
NSMutableArray<NSString *> *selfComponents = [[self pathComponents] mutableCopy];
while (baseComponents.count) {
if (![baseComponents.firstObject isEqualToString:selfComponents.firstObject]) {
break;
}
[baseComponents removeObjectAtIndex:0];
[selfComponents removeObjectAtIndex:0];
}
while (baseComponents.count) {
[baseComponents removeObjectAtIndex:0];
[selfComponents insertObject:@".." atIndex:0];
}
return [selfComponents componentsJoinedByString:@"/"];
}
@end
#define GB_MODEL_PAL_BIT_OLD 0x1000 #define GB_MODEL_PAL_BIT_OLD 0x1000
/* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */ /* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */
@ -499,8 +523,8 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
[_audioClient stop]; [_audioClient stop];
_audioClient = nil; _audioClient = nil;
self.view.mouseHidingEnabled = false; self.view.mouseHidingEnabled = false;
GB_save_battery(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path UTF8String]); GB_save_battery(&gb, self.savPath.UTF8String);
GB_save_cheats(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path UTF8String]); GB_save_cheats(&gb, self.chtPath.UTF8String);
unsigned time_to_alarm = GB_time_to_alarm(&gb); unsigned time_to_alarm = GB_time_to_alarm(&gb);
if (time_to_alarm) { if (time_to_alarm) {
@ -943,28 +967,107 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
} }
} }
- (bool)isCartContainer
{
return [self.fileName.pathExtension.lowercaseString isEqualToString:@"gbcart"];
}
- (NSString *)savPath
{
if (self.isCartContainer) {
return [self.fileName stringByAppendingPathComponent:@"battery.sav"];
}
return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path;
}
- (NSString *)chtPath
{
if (self.isCartContainer) {
return [self.fileName stringByAppendingPathComponent:@"cheats.cht"];
}
return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path;
}
- (NSString *)saveStatePath:(unsigned)index
{
if (self.isCartContainer) {
return [self.fileName stringByAppendingPathComponent:[NSString stringWithFormat:@"state.s%u", index]];
}
return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%u", index]].path;
}
- (NSString *)romPath
{
NSString *fileName = self.fileName;
if (self.isCartContainer) {
NSArray *paths = [[NSString stringWithContentsOfFile:[fileName stringByAppendingPathComponent:@"rom.gbl"]
encoding:NSUTF8StringEncoding
error:nil] componentsSeparatedByString:@"\n"];
fileName = nil;
bool needsRebuild = false;
for (NSString *path in paths) {
NSURL *url = [NSURL URLWithString:path relativeToURL:self.fileURL];
if ([[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
if (fileName && ![fileName isEqualToString:url.path]) {
needsRebuild = true;
break;
}
fileName = url.path;
}
else {
needsRebuild = true;
}
}
if (fileName && needsRebuild) {
[[NSString stringWithFormat:@"%@\n%@\n%@",
[fileName pathRelativeToDirectory:self.fileName],
fileName,
[[NSURL fileURLWithPath:fileName].fileReferenceURL.absoluteString substringFromIndex:strlen("file://")]]
writeToFile:[self.fileName stringByAppendingPathComponent:@"rom.gbl"]
atomically:false
encoding:NSUTF8StringEncoding
error:nil];
}
}
return fileName;
}
- (int) loadROM - (int) loadROM
{ {
__block int ret = 0; __block int ret = 0;
NSString *fileName = self.romPath;
if (!fileName) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"Could not locate the ROM referenced by this Game Boy Cartridge"];
[alert setAlertStyle:NSAlertStyleCritical];
[alert runModal];
return 1;
}
NSString *rom_warnings = [self captureOutputForBlock:^{ NSString *rom_warnings = [self captureOutputForBlock:^{
GB_debugger_clear_symbols(&gb); GB_debugger_clear_symbols(&gb);
if ([[[self.fileType pathExtension] lowercaseString] isEqualToString:@"isx"]) { if ([[[fileName pathExtension] lowercaseString] isEqualToString:@"isx"]) {
ret = GB_load_isx(&gb, self.fileURL.path.UTF8String); ret = GB_load_isx(&gb, fileName.UTF8String);
GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"ram"].path.UTF8String); if (!self.isCartContainer) {
GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"ram"].path.UTF8String);
}
} }
else if ([[[self.fileType pathExtension] lowercaseString] isEqualToString:@"gbs"]) { else if ([[[fileName pathExtension] lowercaseString] isEqualToString:@"gbs"]) {
__block GB_gbs_info_t info; __block GB_gbs_info_t info;
ret = GB_load_gbs(&gb, self.fileURL.path.UTF8String, &info); ret = GB_load_gbs(&gb, fileName.UTF8String, &info);
[self prepareGBSInterface:&info]; [self prepareGBSInterface:&info];
} }
else { else {
ret = GB_load_rom(&gb, [self.fileURL.path UTF8String]); ret = GB_load_rom(&gb, [fileName UTF8String]);
} }
GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path.UTF8String); GB_load_battery(&gb, self.savPath.UTF8String);
GB_load_cheats(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path.UTF8String); GB_load_cheats(&gb, self.chtPath.UTF8String);
[self.cheatWindowController cheatsUpdated]; [self.cheatWindowController cheatsUpdated];
GB_debugger_load_symbol_file(&gb, [[[NSBundle mainBundle] pathForResource:@"registers" ofType:@"sym"] UTF8String]); GB_debugger_load_symbol_file(&gb, [[[NSBundle mainBundle] pathForResource:@"registers" ofType:@"sym"] UTF8String]);
GB_debugger_load_symbol_file(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sym"].path.UTF8String); GB_debugger_load_symbol_file(&gb, [[fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sym"].UTF8String);
}]; }];
if (ret) { if (ret) {
NSAlert *alert = [[NSAlert alloc] init]; NSAlert *alert = [[NSAlert alloc] init];
@ -1295,7 +1398,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
{ {
bool __block success = false; bool __block success = false;
[self performAtomicBlock:^{ [self performAtomicBlock:^{
success = GB_save_state(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]].path.UTF8String) == 0; success = GB_save_state(&gb, [self saveStatePath:[sender tag]].UTF8String) == 0;
}]; }];
if (!success) { if (!success) {
@ -1333,8 +1436,8 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (IBAction)loadState:(id)sender - (IBAction)loadState:(id)sender
{ {
int ret = [self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag]]].path.UTF8String noErrorOnNotFound:true]; int ret = [self loadStateFile:[self saveStatePath:[sender tag]].UTF8String noErrorOnNotFound:true];
if (ret == ENOENT) { if (ret == ENOENT && !self.isCartContainer) {
[self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"sn%ld", (long)[sender tag]]].path.UTF8String noErrorOnNotFound:false]; [self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"sn%ld", (long)[sender tag]]].path.UTF8String noErrorOnNotFound:false];
} }
} }
@ -2260,4 +2363,30 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
GB_set_object_rendering_disabled(&gb, !GB_is_object_rendering_disabled(&gb)); GB_set_object_rendering_disabled(&gb, !GB_is_object_rendering_disabled(&gb));
} }
- (IBAction)newCartridgeInstance:(id)sender
{
bool shouldResume = running;
[self stop];
NSSavePanel *savePanel = [NSSavePanel savePanel];
[savePanel setAllowedFileTypes:@[@"gbcart"]];
[savePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) {
if (result == NSModalResponseOK) {
[savePanel orderOut:self];
NSString *romPath = self.romPath;
[[NSFileManager defaultManager] trashItemAtURL:savePanel.URL resultingItemURL:nil error:nil];
[[NSFileManager defaultManager] createDirectoryAtURL:savePanel.URL withIntermediateDirectories:false attributes:nil error:nil];
[[NSString stringWithFormat:@"%@\n%@\n%@",
[romPath pathRelativeToDirectory:savePanel.URL.path],
romPath,
[[NSURL fileURLWithPath:romPath].fileReferenceURL.absoluteString substringFromIndex:strlen("file://")]
] writeToURL:[savePanel.URL URLByAppendingPathComponent:@"rom.gbl"] atomically:false encoding:NSUTF8StringEncoding error:nil];
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:savePanel.URL display:true completionHandler:nil];
}
if (shouldResume) {
[self start];
}
}];
}
@end @end

View File

@ -88,6 +88,24 @@
<key>NSDocumentClass</key> <key>NSDocumentClass</key>
<string>Document</string> <string>Document</string>
</dict> </dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>gbcart</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>ColorCartridge</string>
<key>CFBundleTypeName</key>
<string>Game Boy Cartridge</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array/>
<key>LSTypeIsPackage</key>
<integer>1</integer>
<key>NSDocumentClass</key>
<string>Document</string>
</dict>
</array> </array>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>SameBoy</string> <string>SameBoy</string>

View File

@ -91,6 +91,12 @@
</menu> </menu>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/> <menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
<menuItem title="New Cartridge Instance…" keyEquivalent="n" id="Vld-be-NZu">
<connections>
<action selector="newCartridgeInstance:" target="-1" id="GJc-xU-ZEr"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="vQH-Yd-TH4"/>
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG"> <menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
<connections> <connections>
<action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/> <action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>