Recognize openable types in Info.plist, debug menu in top bar

This commit is contained in:
Serena 2023-04-22 18:11:06 +03:00
parent 3d7e73a37a
commit a71ec1acdf
7 changed files with 425 additions and 117 deletions

View File

@ -1269,8 +1269,9 @@ elseif(TARGET SDL2::SDL2)
endif()
set(nativeExtraLibs ${nativeExtraLibs} SDL2::SDL2)
if(APPLE)
set(nativeExtra ${nativeExtra} SDL/SDLMain.h SDL/SDLMain.mm SDL/SDLCocoaMetalLayer.h SDL/SDLCocoaMetalLayer.mm SDL/CocoaBarItems.mm SDL/CocoaBarItems.h UI/DarwinFileSystemServices.mm UI/DarwinFileSystemServices.h Common/Battery/AppleBatteryClient.m)
set(nativeExtra ${nativeExtra} SDL/SDLMain.h SDL/SDLMain.mm SDL/SDLCocoaMetalLayer.h SDL/SDLCocoaMetalLayer.mm SDL/CocoaBarItems.mm SDL/CocoaBarItems.h UI/DarwinFileSystemServices.mm UI/DarwinFileSystemServices.h Common/Battery/AppleBatteryClient.m UI/PSPNSApplicationDelegate.mm UI/PSPNSApplicationDelegate.h)
set_source_files_properties(UI/DarwinFileSystemServices.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
set_source_files_properties(UI/PSPNSApplicationDelegate.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
set_source_files_properties(SDL/CocoaBarItems.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
set_source_files_properties(Common/Battery/AppleBatteryClient.m PROPERTIES COMPILE_FLAGS -fobjc-arc)
set(nativeExtraLibs ${nativeExtraLibs} ${COCOA_LIBRARY} ${QUARTZ_CORE_LIBRARY} ${IOKIT_LIBRARY})
@ -2599,6 +2600,12 @@ if(IOS AND NOT LIBRETRO)
)
endif()
if(MACOSX)
set_target_properties(${TargetBin} PROPERTIES
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/macOS/Info.plist"
)
endif()
if(UNIX AND NOT ANDROID AND NOT APPLE)
configure_file(
"${CMAKE_SOURCE_DIR}/ppsspp.desktop.in"

View File

@ -11,7 +11,7 @@
extern "C" {
#endif
void initBarItemsForApp();
void initializeOSXExtras();
#ifdef __cplusplus
}

View File

@ -6,7 +6,12 @@
//
#import <Cocoa/Cocoa.h>
#include "UI/DarwinFileSystemServices.h"
#include "UI/PSPNSApplicationDelegate.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/MemMap.h"
#include "GPU/GPUInterface.h"
#include "Common/File/Path.h"
#include "Common/System/System.h"
#include "Common/System/NativeApp.h"
@ -14,10 +19,21 @@
#include "Common/Data/Text/I18n.h"
#include "Common/StringUtils.h"
void TakeScreenshot();
/* including "Core/Core.h" results in a compilation error soo */
bool Core_IsStepping();
void Core_EnableStepping(bool step, const char *reason = nullptr, u32 relatedAddress = 0);
#ifdef __cplusplus
extern "C" {
#endif
#define MENU_ITEM(variableName, localizedTitleName, SEL, ConfigurationValueName, Tag) \
NSMenuItem *variableName = [[NSMenuItem alloc] initWithTitle:localizedTitleName action:SEL keyEquivalent:@""]; \
variableName.target = self; \
variableName.tag = Tag; \
variableName.state = [self controlStateForBool: ConfigurationValueName];
// NSMenuItem requires the use of an objective-c selector (aka the devil's greatest trick)
// So we have to make this class
@interface BarItemsManager : NSObject <NSMenuDelegate>
@ -28,7 +44,8 @@ extern "C" {
@property (assign) std::shared_ptr<I18NCategory> graphicsLocalization;
@end
void initBarItemsForApp() {
void initializeOSXExtras() {
[NSApplication.sharedApplication setDelegate:[PSPNSApplicationDelegate sharedAppDelegate]];
[[BarItemsManager sharedInstance] setupAppBarItems];
}
@ -45,6 +62,7 @@ void initBarItemsForApp() {
}
-(void)setupAppBarItems {
NSMenuItem *openMenuItem = [[NSMenuItem alloc] init];
openMenuItem.submenu = [self makeOpenSubmenu];
openMenuItem.submenu.delegate = self;
@ -53,11 +71,16 @@ void initBarItemsForApp() {
graphicsMenuItem.submenu = [self makeGraphicsMenu];
graphicsMenuItem.submenu.delegate = self;
NSMenuItem *debugMenuItem = [[NSMenuItem alloc] init];
debugMenuItem.submenu = [self makeDebugMenu];
debugMenuItem.submenu.delegate = self;
NSMenuItem *helpMenuItem = [[NSMenuItem alloc] init];
helpMenuItem.submenu = [self makeHelpMenu];
[NSApplication.sharedApplication.menu addItem:openMenuItem];
[NSApplication.sharedApplication.menu addItem:graphicsMenuItem];
[NSApplication.sharedApplication.menu addItem:debugMenuItem];
[NSApplication.sharedApplication.menu addItem:helpMenuItem];
NSString *windowMenuItemTitle = @"Window";
@ -72,6 +95,16 @@ void initBarItemsForApp() {
break;
}
}
/* IGNORE THE BELOW and eat some nice cherries :3
NSArray <NSMenuItem *> *firstSubmenu = NSApp.menu.itemArray.firstObject.submenu.itemArray;
for (NSMenuItem *item in firstSubmenu) {
// about item, set action
if ([item.title hasPrefix:@"About "]) {
break;
}
}
*/
}
- (void)menuNeedsUpdate:(NSMenu *)menu {
@ -103,6 +136,19 @@ void initBarItemsForApp() {
break;
}
}
} else if ([menu.title isEqualToString: [self localizedString:"Debug" category: self.mainSettingsLocalization]]) {
for (NSMenuItem *item in menu.itemArray) {
switch ([item tag]) {
case 2:
item.state = [self controlStateForBool:g_Config.bAutoRun];
break;
case 3:
item.state = [self controlStateForBool:g_Config.bIgnoreBadMemAccess];
break;
default:
break;
}
}
}
}
@ -166,35 +212,29 @@ void initBarItemsForApp() {
[parent addItem:[NSMenuItem separatorItem]];
#define MENU_ITEM(variableName, localizedTitleName, SEL, ConfigurationValueName, Tag) \
NSMenuItem *variableName = [[NSMenuItem alloc] initWithTitle:GRAPHICS_LOCALIZED(localizedTitleName) action:SEL keyEquivalent:@""]; \
variableName.target = self; \
variableName.tag = Tag; \
variableName.state = [self controlStateForBool: ConfigurationValueName];
MENU_ITEM(softwareRendering, "Software Rendering", @selector(toggleSoftwareRendering:), g_Config.bSoftwareRendering, 1)
MENU_ITEM(softwareRendering, GRAPHICS_LOCALIZED("Software Rendering"), @selector(toggleSoftwareRendering:), g_Config.bSoftwareRendering, 1)
[parent addItem:softwareRendering];
MENU_ITEM(vsyncItem, "VSync", @selector(toggleVSync:), g_Config.bVSync, 2)
MENU_ITEM(vsyncItem, GRAPHICS_LOCALIZED("VSync"), @selector(toggleVSync:), g_Config.bVSync, 2)
[parent addItem:vsyncItem];
MENU_ITEM(fullScreenItem, "Fullscreen", @selector(toggleFullScreen:), g_Config.bFullScreen, 3)
MENU_ITEM(fullScreenItem, GRAPHICS_LOCALIZED("Fullscreen"), @selector(toggleFullScreen:), g_Config.bFullScreen, 3)
[parent addItem:fullScreenItem];
[parent addItem:[NSMenuItem separatorItem]];
MENU_ITEM(autoFrameSkip, "Auto FrameSkip", @selector(toggleAutoFrameSkip:), g_Config.bAutoFrameSkip, 4)
MENU_ITEM(autoFrameSkip, GRAPHICS_LOCALIZED("Auto FrameSkip"), @selector(toggleAutoFrameSkip:), g_Config.bAutoFrameSkip, 4)
[parent addItem:autoFrameSkip];
[parent addItem:[NSMenuItem separatorItem]];
MENU_ITEM(fpsCounterItem, "Show FPS Counter", @selector(setToggleShowCounterItem:), g_Config.iShowStatusFlags & (int)ShowStatusFlags::FPS_COUNTER, 5)
MENU_ITEM(fpsCounterItem, GRAPHICS_LOCALIZED("Show FPS Counter"), @selector(setToggleShowCounterItem:), g_Config.iShowStatusFlags & (int)ShowStatusFlags::FPS_COUNTER, 5)
fpsCounterItem.tag = (int)ShowStatusFlags::FPS_COUNTER + 100;
MENU_ITEM(speedCounterItem, "Show Speed", @selector(setToggleShowCounterItem:), g_Config.iShowStatusFlags & (int)ShowStatusFlags::SPEED_COUNTER, 6)
MENU_ITEM(speedCounterItem, GRAPHICS_LOCALIZED("Show Speed"), @selector(setToggleShowCounterItem:), g_Config.iShowStatusFlags & (int)ShowStatusFlags::SPEED_COUNTER, 6)
speedCounterItem.tag = (int)ShowStatusFlags::SPEED_COUNTER + 100; // because of menuNeedsUpdate:
MENU_ITEM(batteryPercentItem, "Show battery %", @selector(setToggleShowCounterItem:), g_Config.iShowStatusFlags & (int)ShowStatusFlags::BATTERY_PERCENT, 7)
MENU_ITEM(batteryPercentItem, GRAPHICS_LOCALIZED("Show battery %"), @selector(setToggleShowCounterItem:), g_Config.iShowStatusFlags & (int)ShowStatusFlags::BATTERY_PERCENT, 7)
batteryPercentItem.tag = (int)ShowStatusFlags::BATTERY_PERCENT + 100;
[parent addItem:[NSMenuItem separatorItem]];
@ -202,83 +242,153 @@ variableName.state = [self controlStateForBool: ConfigurationValueName];
[parent addItem:speedCounterItem];
[parent addItem:batteryPercentItem];
#undef MENU_ITEM
#undef GRAPHICS_LOCALIZED
return parent;
}
/*
-(NSMenu *)makeAudioMenu {
NSMenu *parent = [[NSMenu alloc] initWithTitle:@(self.mainSettingsLocalization->T("Audio"))];
auto audioLocalization = GetI18NCategory("Audio");
-(NSMenu *)makeDebugMenu {
std::shared_ptr<I18NCategory> sysInfoLocalization = GetI18NCategory(I18NCat::SYSINFO);
std::shared_ptr<I18NCategory> desktopUILocalization = GetI18NCategory(I18NCat::DESKTOPUI);
#define DESKTOPUI_LOCALIZED(key) @(desktopUILocalization->T(key))
NSMenuItem *enableSoundItem = [[NSMenuItem alloc] initWithTitle:@(audioLocalization->T("Enable Sound")) action:@selector(toggleSound:) keyEquivalent:@""];
enableSoundItem.target = self;
enableSoundItem.state = [self controlStateForBool: g_Config.bEnableSound];
[parent addItem:enableSoundItem];
NSMenu *parent = [[NSMenu alloc] initWithTitle:@(sysInfoLocalization->T("Debug"))];
NSMenuItem *deviceListItem = [[NSMenuItem alloc] initWithTitle:@(audioLocalization->T("Device")) action:nil keyEquivalent:@""];
deviceListItem.submenu = [self makeAudioListMenuWithItem:deviceListItem];
[parent addItem:deviceListItem];
NSMenuItem *breakAction = [[NSMenuItem alloc] initWithTitle:DESKTOPUI_LOCALIZED("Break") action:@selector(breakAction:) keyEquivalent:@""];
breakAction.tag = 1;
breakAction.target = self;
[[NSNotificationCenter defaultCenter] addObserverForName:@"AudioConfChanged" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
NSString *value = [note object];
if ([value isEqualToString:@"EnableSound"]) {
enableSoundItem.state = [self controlStateForBool:g_Config.bEnableSound];
}
}];
MENU_ITEM(breakOnLoadAction, DESKTOPUI_LOCALIZED("Break on Load"), @selector(toggleBreakOnLoad:), g_Config.bAutoRun, 2)
MENU_ITEM(ignoreIllegalRWAction, DESKTOPUI_LOCALIZED("Ignore Illegal Reads/Writes"), @selector(toggleIgnoreIllegalRWs:), g_Config.bIgnoreBadMemAccess, 3)
[parent addItem:breakAction];
[parent addItem:[NSMenuItem separatorItem]];
[parent addItem:breakOnLoadAction];
[parent addItem:ignoreIllegalRWAction];
[parent addItem:[NSMenuItem separatorItem]];
NSMenuItem *loadSymbolMapAction = [[NSMenuItem alloc] initWithTitle:DESKTOPUI_LOCALIZED("Load Map file") action:@selector(loadMapFile) keyEquivalent:@""];
loadSymbolMapAction.target = self;
NSMenuItem *saveMapFileAction = [[NSMenuItem alloc] initWithTitle:DESKTOPUI_LOCALIZED("Save Map file") action:@selector(saveMapFile) keyEquivalent:@""];
saveMapFileAction.target = self;
NSMenuItem *loadSymFileAction = [[NSMenuItem alloc] initWithTitle:DESKTOPUI_LOCALIZED("Load .sym File...") action:@selector(loadSymbolsFile) keyEquivalent:@""];
loadSymFileAction.target = self;
NSMenuItem *saveSymFileAction = [[NSMenuItem alloc] initWithTitle:DESKTOPUI_LOCALIZED("Save .sym File...") action:@selector(saveSymbolsfile) keyEquivalent:@""];
saveSymFileAction.target = self;
NSMenuItem *resetSymbolTableAction = [[NSMenuItem alloc] initWithTitle:DESKTOPUI_LOCALIZED("Reset Symbol Table") action:@selector(resetSymbolTable) keyEquivalent:@""];
resetSymbolTableAction.target = self;
NSMenuItem *takeScreenshotAction = [[NSMenuItem alloc] initWithTitle:DESKTOPUI_LOCALIZED("Take Screenshot") action:@selector(takeScreenshot) keyEquivalent:@""];
takeScreenshotAction.target = self;
NSMenuItem *dumpNextFrameToLogAction = [[NSMenuItem alloc] initWithTitle:DESKTOPUI_LOCALIZED("Dump next frame to log") action:@selector(dumpNextFrameToLog) keyEquivalent:@""];
dumpNextFrameToLogAction.target = self;
NSMenuItem *copyBaseAddr = [[NSMenuItem alloc] initWithTitle:DESKTOPUI_LOCALIZED("Copy PSP memory base address") action:@selector(copyAddr) keyEquivalent:@""];
copyBaseAddr.target = self;
MENU_ITEM(showDebugStatsAction, DESKTOPUI_LOCALIZED("Show Debug Statistics"), @selector(toggleShowDebugStats:), g_Config.bShowDebugStats, 2)
[parent addItem:loadSymbolMapAction];
[parent addItem:saveMapFileAction];
[parent addItem:[NSMenuItem separatorItem]];
[parent addItem:loadSymFileAction];
[parent addItem:saveSymFileAction];
[parent addItem:[NSMenuItem separatorItem]];
[parent addItem:resetSymbolTableAction];
[parent addItem:[NSMenuItem separatorItem]];
[parent addItem:takeScreenshotAction];
[parent addItem:dumpNextFrameToLogAction];
[parent addItem:showDebugStatsAction];
[parent addItem:[NSMenuItem separatorItem]];
[parent addItem:copyBaseAddr];
return parent;
}
-(NSMenu *)makeAudioListMenuWithItem: (NSMenuItem *)callerItem {
__block NSMenu *theMenu = [[NSMenu alloc] init];
std::vector<std::string> audioDeviceList;
SplitString(System_GetProperty(SYSPROP_AUDIO_DEVICE_LIST), '\0', audioDeviceList);
for (int i = 0; i < audioDeviceList.size(); i++) {
std::string itemName = audioDeviceList[i];
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@(itemName.c_str()) action:@selector(setAudioItem:) keyEquivalent:@""];
item.tag = i;
item.target = self;
item.state = [self controlStateForBool:g_Config.sAudioDevice == itemName];
[theMenu addItem:item];
-(void)breakAction: (NSMenuItem *)item {
if (Core_IsStepping()) {
Core_EnableStepping(false, "ui.break");
item.title = @"Break";
} else {
Core_EnableStepping(true, "ui.break");
item.title = @"Resume";
}
}
[[NSNotificationCenter defaultCenter] addObserverForName:@"AudioConfigurationHasChanged" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
NSString *value = [note object];
if ([value isEqualToString: @"DeviceAddedOrChanged"]) {
callerItem.submenu = [self makeAudioListMenuWithItem:callerItem];
} else if ([value isEqualToString:@"CurrentDeviceWasChanged"]) {
// set the new item to be the selected one
dispatch_async(dispatch_get_main_queue(), ^{
for (NSMenuItem *item in theMenu.itemArray)
item.state = [self controlStateForBool:g_Config.sAudioDevice == audioDeviceList[item.tag]];
});
}
}];
return theMenu;
}
*/
/*
-(void) setAudioItem: (NSMenuItem *)sender {
std::vector<std::string> audioDeviceList;
SplitString(System_GetProperty(SYSPROP_AUDIO_DEVICE_LIST), '\0', audioDeviceList);
std::string theItemSelected = audioDeviceList[sender.tag];
if (theItemSelected == g_Config.sAudioDevice)
return; // device already selected
g_Config.sAudioDevice = theItemSelected;
for (NSMenuItem *item in sender.menu.itemArray) {
item.state = [self controlStateForBool:g_Config.sAudioDevice == theItemSelected];
-(void)copyAddr {
NSString *stringToCopy = [NSString stringWithFormat: @"%016llx", (uint64_t)(uintptr_t)Memory::base];
[NSPasteboard.generalPasteboard declareTypes:@[NSPasteboardTypeString] owner:nil];
[NSPasteboard.generalPasteboard setString:stringToCopy forType:NSPasteboardTypeString];
}
System_SendMessage("audio_resetDevice", "");
-(NSURL *)presentOpenPanelWithAllowedFileTypes: (NSArray<NSString *> *)allowedFileTypes {
NSOpenPanel *openPanel = [[NSOpenPanel alloc] init];
openPanel.allowedFileTypes = allowedFileTypes;
if ([openPanel runModal] == NSModalResponseOK) {
NSURL *urlWeWant = openPanel.URLs.firstObject;
if (urlWeWant) {
return urlWeWant;
}
}
return nil;
}
-(NSURL *)presentSavePanelWithDefaultFilename: (NSString *)filename {
NSSavePanel *savePanel = [[NSSavePanel alloc] init];
savePanel.nameFieldStringValue = filename;
if ([savePanel runModal] == NSModalResponseOK && savePanel.URL) {
return savePanel.URL;
}
return nil;
}
-(void)dumpNextFrameToLog {
gpu->DumpNextFrame();
}
-(void)takeScreenshot {
TakeScreenshot();
}
-(void)resetSymbolTable {
g_symbolMap->Clear();
}
-(void)saveSymbolsfile {
NSURL *url = [self presentSavePanelWithDefaultFilename:@"Symbols.sym"];
if (url)
g_symbolMap->SaveNocashSym(Path(url.fileSystemRepresentation));
}
-(void)loadSymbolsFile {
NSURL *url = [self presentOpenPanelWithAllowedFileTypes:@[@"sym"]];
if (url)
g_symbolMap->LoadNocashSym(Path(url.fileSystemRepresentation));
}
-(void)loadMapFile {
/* Using NSOpenPanel to filter by `allowedFileTypes` */
NSURL *url = [self presentOpenPanelWithAllowedFileTypes:@[@"map"]];
if (url)
g_symbolMap->LoadSymbolMap(Path(url.fileSystemRepresentation));
}
-(void)saveMapFile {
NSURL *symbolsMapFileURL = [self presentSavePanelWithDefaultFilename:@"Symbols.map"];
if (symbolsMapFileURL)
g_symbolMap->SaveSymbolMap(Path(symbolsMapFileURL.fileSystemRepresentation));
}
*/
#define TOGGLE_METHOD(name, ConfigValueName, ...) \
-(void)toggle##name: (NSMenuItem *)item { \
@ -288,10 +398,13 @@ item.state = [self controlStateForBool: ConfigValueName]; \
}
TOGGLE_METHOD(Sound, g_Config.bEnableSound)
TOGGLE_METHOD(BreakOnLoad, g_Config.bAutoRun)
TOGGLE_METHOD(IgnoreIllegalRWs, g_Config.bIgnoreBadMemAccess)
TOGGLE_METHOD(AutoFrameSkip, g_Config.bAutoFrameSkip, g_Config.UpdateAfterSettingAutoFrameSkip())
TOGGLE_METHOD(SoftwareRendering, g_Config.bSoftwareRendering)
TOGGLE_METHOD(FullScreen, g_Config.bFullScreen, System_MakeRequest(SystemRequestType::TOGGLE_FULLSCREEN_STATE, 0, g_Config.UseFullScreen() ? "1" : "0", "", 3))
TOGGLE_METHOD(VSync, g_Config.bVSync)
TOGGLE_METHOD(ShowDebugStats, g_Config.bShowDebugStats, NativeMessageReceived("clear jit", ""))
#undef TOGGLE_METHOD
-(void)setToggleShowCounterItem: (NSMenuItem *)item {

View File

@ -926,7 +926,7 @@ int main(int argc, char *argv[]) {
#if PPSSPP_PLATFORM(MAC)
// setup menu items for macOS
initBarItemsForApp();
initializeOSXExtras();
#endif
while (true) {

View File

@ -0,0 +1,14 @@
//
// PSPNSApplicationDelegate.h
// PPSSPP
//
// Created by Serena on 22/04/2023.
//
#import <Cocoa/Cocoa.h>
#pragma once
@interface PSPNSApplicationDelegate : NSObject <NSApplicationDelegate>
+(instancetype)sharedAppDelegate;
@end

View File

@ -0,0 +1,39 @@
//
// PSPNSApplicationDelegate.mm
// PPSSPP
//
// Created by Serena on 22/04/2023.
//
#import <Cocoa/Cocoa.h>
#import "PSPNSApplicationDelegate.h"
#include "Core/SaveState.h"
#include "Common/System/NativeApp.h"
#include <Core/Config.h>
@implementation PSPNSApplicationDelegate
+ (instancetype)sharedAppDelegate {
static PSPNSApplicationDelegate *del;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
del = [PSPNSApplicationDelegate new];
});
return del;
}
- (void)application:(NSApplication *)application openURLs:(NSArray<NSURL *> *)urls {
NSURL *firstURL = urls.firstObject;
if (!firstURL) return; // No URLs, don't do anything
NativeMessageReceived("boot", firstURL.fileSystemRepresentation);
}
- (NSMenu *)applicationDockMenu:(NSApplication *)sender {
for (std::string iso : g_Config.RecentIsos()) {
printf("%s\n", iso.c_str());
}
return nil;
}
@end

135
macOS/Info.plist Normal file
View File

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>PSP ISO Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key>
<array>
<string>public.iso-image</string>
</array>
<key>NSDocumentClass</key>
<string></string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>cso</string>
</array>
<key>CFBundleTypeName</key>
<string>PSP CSO Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key>
<array>
<string>com.ppsspp.csoimg</string>
</array>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>pbp</string>
</array>
<key>CFBundleTypeName</key>
<string>PSP PBP Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key>
<array>
<string>com.ppsspp.pbp</string>
</array>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleGetInfoString</key>
<string></string>
<key>CFBundleIconFile</key>
<string>ppsspp.icns</string>
<key>CFBundleIdentifier</key>
<string>org.ppsspp.ppsspp</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string></string>
<key>CFBundleName</key>
<string>PPSSPP</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string></string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>org.ppsspp.openpspgame</string>
<key>CFBundleURLSchemes</key>
<array>
<string>ppsspp</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string></string>
<key>CSResourcesFileMapped</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string></string>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeDescription</key>
<string>PSP CSO Image</string>
<key>UTTypeIcons</key>
<dict>
<key>UTTypeIconText</key>
<string>PSP CSO Image</string>
</dict>
<key>UTTypeIdentifier</key>
<string>com.ppsspp.csoimg</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>cso</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeDescription</key>
<string>PBP File</string>
<key>UTTypeIcons</key>
<dict>
<key>UTTypeIconText</key>
<string>PSP PBP Image</string>
</dict>
<key>UTTypeIdentifier</key>
<string>com.ppsspp.pbp</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>pbp</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>