diff --git a/.gitignore b/.gitignore index 52fe2f7..ac6b652 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,5 @@ fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output + +**/.DS_Store diff --git a/macOS/fPKG Builder.xcodeproj/project.pbxproj b/macOS/fPKG Builder.xcodeproj/project.pbxproj new file mode 100644 index 0000000..d1fa7d0 --- /dev/null +++ b/macOS/fPKG Builder.xcodeproj/project.pbxproj @@ -0,0 +1,395 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + C2DC09452C6D2F45009897FF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DC09442C6D2F45009897FF /* AppDelegate.swift */; }; + C2DC09492C6D2F46009897FF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C2DC09482C6D2F46009897FF /* Assets.xcassets */; }; + C2DC094C2C6D2F46009897FF /* Base in Resources */ = {isa = PBXBuildFile; fileRef = C2DC094B2C6D2F46009897FF /* Base */; }; + C2DC09572C6D3068009897FF /* PSClassicsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DC09532C6D3068009897FF /* PSClassicsWindowController.swift */; }; + C2DC09582C6D3068009897FF /* PS2ClassicsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DC09542C6D3068009897FF /* PS2ClassicsViewController.swift */; }; + C2DC09592C6D3068009897FF /* PSPClassicsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DC09552C6D3068009897FF /* PSPClassicsViewController.swift */; }; + C2DC095A2C6D3068009897FF /* PS1ClassicsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DC09562C6D3068009897FF /* PS1ClassicsViewController.swift */; }; + C2E921542C70FAE600DEC0FC /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E921532C70FAE600DEC0FC /* Utils.swift */; }; + C2E921562C7110C800DEC0FC /* isoinfo in Resources */ = {isa = PBXBuildFile; fileRef = C2E921552C7110C800DEC0FC /* isoinfo */; }; + C2E921582C71219700DEC0FC /* unar in Resources */ = {isa = PBXBuildFile; fileRef = C2E921572C71219700DEC0FC /* unar */; }; + C2E9215A2C71E96C00DEC0FC /* pspdecrypt in Resources */ = {isa = PBXBuildFile; fileRef = C2E921592C71E96C00DEC0FC /* pspdecrypt */; }; + C2E9215E2C71F6F000DEC0FC /* sfoutil in Resources */ = {isa = PBXBuildFile; fileRef = C2E9215D2C71F6F000DEC0FC /* sfoutil */; }; + C2E921602C72298400DEC0FC /* Preloader.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C2E9215F2C72298400DEC0FC /* Preloader.storyboard */; }; + C2E921622C722C1200DEC0FC /* PreloaderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E921612C722C1200DEC0FC /* PreloaderViewController.swift */; }; + C2E921642C72409200DEC0FC /* PSClassicsTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E921632C72409200DEC0FC /* PSClassicsTabViewController.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + C2DC09412C6D2F45009897FF /* fPKG Builder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "fPKG Builder.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + C2DC09442C6D2F45009897FF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + C2DC09482C6D2F46009897FF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + C2DC094B2C6D2F46009897FF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + C2DC094D2C6D2F46009897FF /* fPKG_Builder.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = fPKG_Builder.entitlements; sourceTree = ""; }; + C2DC09532C6D3068009897FF /* PSClassicsWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PSClassicsWindowController.swift; sourceTree = ""; }; + C2DC09542C6D3068009897FF /* PS2ClassicsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PS2ClassicsViewController.swift; sourceTree = ""; }; + C2DC09552C6D3068009897FF /* PSPClassicsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PSPClassicsViewController.swift; sourceTree = ""; }; + C2DC09562C6D3068009897FF /* PS1ClassicsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PS1ClassicsViewController.swift; sourceTree = ""; }; + C2E921532C70FAE600DEC0FC /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; + C2E921552C7110C800DEC0FC /* isoinfo */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = isoinfo; sourceTree = ""; }; + C2E921572C71219700DEC0FC /* unar */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = unar; sourceTree = ""; }; + C2E921592C71E96C00DEC0FC /* pspdecrypt */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = pspdecrypt; sourceTree = ""; }; + C2E9215D2C71F6F000DEC0FC /* sfoutil */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = sfoutil; sourceTree = ""; }; + C2E9215F2C72298400DEC0FC /* Preloader.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Preloader.storyboard; sourceTree = ""; }; + C2E921612C722C1200DEC0FC /* PreloaderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreloaderViewController.swift; sourceTree = ""; }; + C2E921632C72409200DEC0FC /* PSClassicsTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PSClassicsTabViewController.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C2DC093E2C6D2F45009897FF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + C2DC09382C6D2F45009897FF = { + isa = PBXGroup; + children = ( + C2DC09432C6D2F45009897FF /* fPKG Builder */, + C2DC09422C6D2F45009897FF /* Products */, + ); + sourceTree = ""; + }; + C2DC09422C6D2F45009897FF /* Products */ = { + isa = PBXGroup; + children = ( + C2DC09412C6D2F45009897FF /* fPKG Builder.app */, + ); + name = Products; + sourceTree = ""; + }; + C2DC09432C6D2F45009897FF /* fPKG Builder */ = { + isa = PBXGroup; + children = ( + C2E9215D2C71F6F000DEC0FC /* sfoutil */, + C2E921592C71E96C00DEC0FC /* pspdecrypt */, + C2E921572C71219700DEC0FC /* unar */, + C2E921552C7110C800DEC0FC /* isoinfo */, + C2DC09442C6D2F45009897FF /* AppDelegate.swift */, + C2E921612C722C1200DEC0FC /* PreloaderViewController.swift */, + C2DC09562C6D3068009897FF /* PS1ClassicsViewController.swift */, + C2DC09542C6D3068009897FF /* PS2ClassicsViewController.swift */, + C2E921632C72409200DEC0FC /* PSClassicsTabViewController.swift */, + C2DC09532C6D3068009897FF /* PSClassicsWindowController.swift */, + C2DC09552C6D3068009897FF /* PSPClassicsViewController.swift */, + C2E921532C70FAE600DEC0FC /* Utils.swift */, + C2DC09482C6D2F46009897FF /* Assets.xcassets */, + C2DC094A2C6D2F46009897FF /* Main.storyboard */, + C2E9215F2C72298400DEC0FC /* Preloader.storyboard */, + C2DC094D2C6D2F46009897FF /* fPKG_Builder.entitlements */, + ); + path = "fPKG Builder"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + C2DC09402C6D2F45009897FF /* fPKG Builder */ = { + isa = PBXNativeTarget; + buildConfigurationList = C2DC09502C6D2F46009897FF /* Build configuration list for PBXNativeTarget "fPKG Builder" */; + buildPhases = ( + C2DC093D2C6D2F45009897FF /* Sources */, + C2DC093E2C6D2F45009897FF /* Frameworks */, + C2DC093F2C6D2F45009897FF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "fPKG Builder"; + productName = "fPKG Builder"; + productReference = C2DC09412C6D2F45009897FF /* fPKG Builder.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C2DC09392C6D2F45009897FF /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1540; + LastUpgradeCheck = 1540; + TargetAttributes = { + C2DC09402C6D2F45009897FF = { + CreatedOnToolsVersion = 15.4; + }; + }; + }; + buildConfigurationList = C2DC093C2C6D2F45009897FF /* Build configuration list for PBXProject "fPKG Builder" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = C2DC09382C6D2F45009897FF; + productRefGroup = C2DC09422C6D2F45009897FF /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C2DC09402C6D2F45009897FF /* fPKG Builder */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + C2DC093F2C6D2F45009897FF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C2E9215A2C71E96C00DEC0FC /* pspdecrypt in Resources */, + C2E921562C7110C800DEC0FC /* isoinfo in Resources */, + C2DC09492C6D2F46009897FF /* Assets.xcassets in Resources */, + C2E9215E2C71F6F000DEC0FC /* sfoutil in Resources */, + C2E921582C71219700DEC0FC /* unar in Resources */, + C2E921602C72298400DEC0FC /* Preloader.storyboard in Resources */, + C2DC094C2C6D2F46009897FF /* Base in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C2DC093D2C6D2F45009897FF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C2DC09582C6D3068009897FF /* PS2ClassicsViewController.swift in Sources */, + C2DC09592C6D3068009897FF /* PSPClassicsViewController.swift in Sources */, + C2DC09452C6D2F45009897FF /* AppDelegate.swift in Sources */, + C2DC095A2C6D3068009897FF /* PS1ClassicsViewController.swift in Sources */, + C2DC09572C6D3068009897FF /* PSClassicsWindowController.swift in Sources */, + C2E921622C722C1200DEC0FC /* PreloaderViewController.swift in Sources */, + C2E921642C72409200DEC0FC /* PSClassicsTabViewController.swift in Sources */, + C2E921542C70FAE600DEC0FC /* Utils.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + C2DC094A2C6D2F46009897FF /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + C2DC094B2C6D2F46009897FF /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + C2DC094E2C6D2F46009897FF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + C2DC094F2C6D2F46009897FF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + C2DC09512C6D2F46009897FF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "fPKG Builder/fPKG_Builder.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "PS Classics fPKG Builder"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSMainStoryboardFile = Preloader; + INFOPLIST_KEY_NSPrincipalClass = NSApplication; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "SvenGDK.fPKG-Builder"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + C2DC09522C6D2F46009897FF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "fPKG Builder/fPKG_Builder.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "PS Classics fPKG Builder"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSMainStoryboardFile = Preloader; + INFOPLIST_KEY_NSPrincipalClass = NSApplication; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "SvenGDK.fPKG-Builder"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C2DC093C2C6D2F45009897FF /* Build configuration list for PBXProject "fPKG Builder" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C2DC094E2C6D2F46009897FF /* Debug */, + C2DC094F2C6D2F46009897FF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C2DC09502C6D2F46009897FF /* Build configuration list for PBXNativeTarget "fPKG Builder" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C2DC09512C6D2F46009897FF /* Debug */, + C2DC09522C6D2F46009897FF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = C2DC09392C6D2F45009897FF /* Project object */; +} diff --git a/macOS/fPKG Builder.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/macOS/fPKG Builder.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/macOS/fPKG Builder.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/macOS/fPKG Builder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macOS/fPKG Builder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macOS/fPKG Builder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macOS/fPKG Builder/AppDelegate.swift b/macOS/fPKG Builder/AppDelegate.swift new file mode 100644 index 0000000..496d37b --- /dev/null +++ b/macOS/fPKG Builder/AppDelegate.swift @@ -0,0 +1,27 @@ +// +// AppDelegate.swift +// fPKG Builder +// +// Created by SvenGDK on 14/08/2024. +// + +import Cocoa + +@main +class AppDelegate: NSObject, NSApplicationDelegate { + + func applicationDidFinishLaunching(_ aNotification: Notification) { + // Insert code here to initialize your application + } + + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } + + func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } + + +} + diff --git a/macOS/fPKG Builder/Assets.xcassets/AccentColor.colorset/Contents.json b/macOS/fPKG Builder/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/macOS/fPKG Builder/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/macOS/fPKG Builder/Assets.xcassets/AppIcon.appiconset/Contents.json b/macOS/fPKG Builder/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..3f00db4 --- /dev/null +++ b/macOS/fPKG Builder/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/macOS/fPKG Builder/Assets.xcassets/Contents.json b/macOS/fPKG Builder/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/macOS/fPKG Builder/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/macOS/fPKG Builder/Assets.xcassets/PS1.imageset/Contents.json b/macOS/fPKG Builder/Assets.xcassets/PS1.imageset/Contents.json new file mode 100644 index 0000000..949fd2d --- /dev/null +++ b/macOS/fPKG Builder/Assets.xcassets/PS1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "PS1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/macOS/fPKG Builder/Assets.xcassets/PS1.imageset/PS1.png b/macOS/fPKG Builder/Assets.xcassets/PS1.imageset/PS1.png new file mode 100644 index 0000000..3cc45fb Binary files /dev/null and b/macOS/fPKG Builder/Assets.xcassets/PS1.imageset/PS1.png differ diff --git a/macOS/fPKG Builder/Assets.xcassets/PS2.imageset/Contents.json b/macOS/fPKG Builder/Assets.xcassets/PS2.imageset/Contents.json new file mode 100644 index 0000000..2d64eb2 --- /dev/null +++ b/macOS/fPKG Builder/Assets.xcassets/PS2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "PS2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/macOS/fPKG Builder/Assets.xcassets/PS2.imageset/PS2.png b/macOS/fPKG Builder/Assets.xcassets/PS2.imageset/PS2.png new file mode 100644 index 0000000..ea65080 Binary files /dev/null and b/macOS/fPKG Builder/Assets.xcassets/PS2.imageset/PS2.png differ diff --git a/macOS/fPKG Builder/Assets.xcassets/PSP.imageset/Contents.json b/macOS/fPKG Builder/Assets.xcassets/PSP.imageset/Contents.json new file mode 100644 index 0000000..ca26fd9 --- /dev/null +++ b/macOS/fPKG Builder/Assets.xcassets/PSP.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "psp.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/macOS/fPKG Builder/Assets.xcassets/PSP.imageset/psp.png b/macOS/fPKG Builder/Assets.xcassets/PSP.imageset/psp.png new file mode 100644 index 0000000..0be9d4e Binary files /dev/null and b/macOS/fPKG Builder/Assets.xcassets/PSP.imageset/psp.png differ diff --git a/macOS/fPKG Builder/Base.lproj/Main.storyboard b/macOS/fPKG Builder/Base.lproj/Main.storyboard new file mode 100644 index 0000000..62b89be --- /dev/null +++ b/macOS/fPKG Builder/Base.lproj/Main.storyboard @@ -0,0 +1,1411 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Jak v2 + Rogue v1 + + + + + + + + + + + + + + + + + + + + + Disabled + On Port 1 + On Port 2 + On Port 1 and 2 + + + + + + + + + + + + + + + + + + + + + + None + GPU + Bilinear + Edgesmooth + Point + + + + + + + + + + + + + + + + + + + + + None + 2x2 + + + + + + + + + + + + + + + + + + + + + None + Scanlines + + + + + + + + + + + + + + + + + + + + + Normal + Full + 4:3 + 16:9 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 2 + 4 + 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macOS/fPKG Builder/PS1ClassicsViewController.swift b/macOS/fPKG Builder/PS1ClassicsViewController.swift new file mode 100644 index 0000000..c283b1e --- /dev/null +++ b/macOS/fPKG Builder/PS1ClassicsViewController.swift @@ -0,0 +1,838 @@ +// +// PS1ClassicsViewController.swift +// PS Mac Tools +// +// Created by SvenGDK on 14/08/2024. +// + +import Foundation +import AppKit + +class PS1ClassicsViewController: NSViewController { + + var CurrentGameID: String = "" + var Disc1CueFile: String = "" + var Disc2CueFile: String = "" + var Disc3CueFile: String = "" + var Disc4CueFile: String = "" + + @IBOutlet weak var BuildfPKGButton: NSButton! + @IBOutlet weak var ProgressTextField: NSTextField! + @IBOutlet weak var fPKGBuilderProgressIndicator: NSProgressIndicator! + @IBOutlet weak var fPKGBuilderSpinningIndicator: NSProgressIndicator! + @IBOutlet weak var fPKGBuilderStatusTextField: NSTextField! + + override func viewDidLoad() { + super.viewDidLoad() + + // Select 6x upscaling by default + UpscalingComboBox.selectItem(at: 3) + } + + override var representedObject: Any? { + didSet { + } + } + + @IBOutlet weak var GameTitleTextField: NSTextField! + @IBOutlet weak var NPTitleTextField: NSTextField! + @IBOutlet weak var IconTextField: NSTextField! + @IBOutlet weak var BackgroundTextField: NSTextField! + @IBOutlet weak var Disc1TextField: NSTextField! + @IBOutlet weak var Disc2TextField: NSTextField! + @IBOutlet weak var Disc3TextField: NSTextField! + @IBOutlet weak var Disc4TextField: NSTextField! + @IBOutlet weak var TXTConfigTextField: NSTextField! + @IBOutlet weak var LUAConfigTextField: NSTextField! + @IBOutlet weak var SkipBootlogoCheckBox: NSButton! + @IBOutlet weak var Force60HzCheckBox: NSButton! + @IBOutlet weak var EmulateAnalogSticksCheckBox: NSButton! + @IBOutlet weak var EnableGunconCheckBox: NSButton! + @IBOutlet weak var UpscalingComboBox: NSComboBox! + + @IBAction func BrowseIcon(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select an icon for your game" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().PNGType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + IconTextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseBackground(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select a background for your game" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().PNGType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + BackgroundTextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseDisc1(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select your PS1 BIN file" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().BINType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + Disc1TextField.stringValue = result!.path + checkPS1Game(inputPath: result!.path) + BuildfPKGButton.isEnabled = true + } else { + return + } + } + + @IBAction func BrowseDisc2(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select the Disc 2 BIN file" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().BINType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + Disc2TextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseDisc3(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select the Disc 3 BIN file" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().BINType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + Disc3TextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseDisc4(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select the Disc 4 BIN file" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().BINType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + Disc4TextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseTXTConfig(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select a TXT config file" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().TXTType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + TXTConfigTextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseLUAConfig(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select a LUA config file" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().TXTType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + LUAConfigTextField.stringValue = result!.path + } else { + return + } + } + + func checkPS1Game(inputPath: String) { + + // Read the BIN file and try to get the game ID + let BINReaderOutput = ReadBINFile(fileInput: inputPath).output + var BINGameTitleID = BINReaderOutput.filter({(item: String) -> Bool in + let stringMatch = item.lowercased().range(of: "BOOT = ".lowercased()) + let stringMatch2 = item.lowercased().range(of: "BOOT=".lowercased()) + if stringMatch == nil { + return stringMatch2 != nil ? true : false + } + else { + return stringMatch != nil ? true : false + } + }) + + if BINGameTitleID.count == 0 { + BINGameTitleID = ["Title ID not found", "Title ID not found"] + } + else { + if BINGameTitleID[0].starts(with: "BOOT=") { + NPTitleTextField.stringValue = BINGameTitleID[0].components(separatedBy: "=")[1] + } + else { + NPTitleTextField.stringValue = BINGameTitleID[0].components(separatedBy: " = ")[1] + } + + CurrentGameID = BINGameTitleID[0].replacingOccurrences(of: "BOOT = cdrom:\\", with: "").replacingOccurrences(of: "BOOT=cdrom:\\", with: "").replacingOccurrences(of: "BOOT = cdrom:", with: "").replacingOccurrences(of: ";1", with: "").replacingOccurrences(of: "_", with: "").replacingOccurrences(of: ".", with: "").replacingOccurrences(of: "MGS\"", with: "") + + NPTitleTextField.stringValue = CurrentGameID + + // Check for a game title if we have a game ID + if !CurrentGameID.isEmpty { + var AdjustedGameID: String = CurrentGameID + AdjustedGameID.insert("-", at: AdjustedGameID.index(AdjustedGameID.startIndex, offsetBy: 4)) + + print(AdjustedGameID) + + let GameTitle: String = FindGameTitle(GameID: AdjustedGameID) + if !GameTitle.isEmpty { + GameTitleTextField.stringValue = GameTitle + } + } + } + + } + + func ReadBINFile(fileInput: String) -> (output: [String], error: [String], exitCode: Int32) { + var output : [String] = [] + var error : [String] = [] + + let task = Process() + task.launchPath = "/bin/sh" + task.arguments = ["-c", "strings '" + fileInput + "' | LANG=C fgrep 'BOOT'"] + + let outpipe = Pipe() + task.standardOutput = outpipe + let errpipe = Pipe() + task.standardError = errpipe + + task.launch() + + let outdata = outpipe.fileHandleForReading.readDataToEndOfFile() + if var string = String(data: outdata, encoding: .utf8) { + string = string.trimmingCharacters(in: .newlines) + output = string.components(separatedBy: "\n") + } + + let errdata = errpipe.fileHandleForReading.readDataToEndOfFile() + if var string = String(data: errdata, encoding: .utf8) { + string = string.trimmingCharacters(in: .newlines) + error = string.components(separatedBy: "\n") + } + + task.waitUntilExit() + let status = task.terminationStatus + + return (output, error, status) + } + + @IBAction func BuildfPKG(_ sender: NSButton) { + + let MissingInfoAlert = NSAlert() + MissingInfoAlert.addButton(withTitle: "Close") + + if Disc1TextField.stringValue.isEmpty { + MissingInfoAlert.messageText = "Disc 1 is missing, cannot continue." + MissingInfoAlert.runModal() + return + } + + if GameTitleTextField.stringValue.isEmpty { + MissingInfoAlert.messageText = "The Game Title is missing, cannot continue." + MissingInfoAlert.runModal() + return + } + + if NPTitleTextField.stringValue.isEmpty { + MissingInfoAlert.messageText = "The NP Title is missing, cannot continue." + MissingInfoAlert.runModal() + return + } + + ProgressTextField.isHidden = false + fPKGBuilderProgressIndicator.isHidden = false + fPKGBuilderSpinningIndicator.isHidden = false + fPKGBuilderStatusTextField.isHidden = false + + fPKGBuilderProgressIndicator.isIndeterminate = true + fPKGBuilderProgressIndicator.startAnimation(nil) + fPKGBuilderSpinningIndicator.startAnimation(nil) + fPKGBuilderStatusTextField.stringValue = "Waiting for user input" + + CreatefPKG() + } + + func CreatefPKG() { + + var PKGOutputPath: String = "" + let FolderBrowserOpenPanel = NSOpenPanel() + + FolderBrowserOpenPanel.title = "Select an output folder" + FolderBrowserOpenPanel.showsResizeIndicator = true + FolderBrowserOpenPanel.showsHiddenFiles = false + FolderBrowserOpenPanel.canChooseFiles = false + FolderBrowserOpenPanel.canChooseDirectories = true + FolderBrowserOpenPanel.allowsMultipleSelection = false + + // Set destination folder where the pkg file will be moved into + if (FolderBrowserOpenPanel.runModal() == NSApplication.ModalResponse.OK) { + let result = FolderBrowserOpenPanel.url + PKGOutputPath = result!.path + } else { + return + } + + fPKGBuilderProgressIndicator.stopAnimation(nil) + fPKGBuilderProgressIndicator.isIndeterminate = false + fPKGBuilderProgressIndicator.doubleValue = 0 + fPKGBuilderStatusTextField.stringValue = "Removing old fPKG build folder" + usleep(150000) + + // Remove previous fPKG GP4 project + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/PS1fPKG.gp4") { + do { + try FileManager.default.removeItem(atPath: Utils().CDrivePath + "/Cache/PS1fPKG.gp4") + } catch (let error) { + print(error) + } + } + + // Remove previous fPKG project folder + var isDir: ObjCBool = true + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/PS1fPKG", isDirectory: &isDir) { + do { + try FileManager.default.removeItem(atPath: Utils().CDrivePath + "/Cache/PS1fPKG") + } catch (let error) { + print(error) + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 + fPKGBuilderStatusTextField.stringValue = "Preparing the PS1 emulator" + usleep(150000) + + // Copy the PS1 emulator to the new project folder + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/PS4/emus/ps1hd", isDirectory: &isDir) { + do { + try FileManager.default.copyItem(atPath: Utils().CDrivePath + "/PS4/emus/ps1hd", toPath: Utils().CDrivePath + "/Cache/PS1fPKG") + } catch (let error) { + print(error) + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 + + // Copy the selected icon to the project folder + if !IconTextField.stringValue.isEmpty { + do { + try FileManager.default.copyItem(atPath: IconTextField.stringValue, toPath: Utils().CDrivePath + "/Cache/PS1fPKG/sce_sys/icon0.png") + } catch (let error) { + print(error) + } + } + + // Copy the selected background to the project folder + if !BackgroundTextField.stringValue.isEmpty { + do { + try FileManager.default.copyItem(atPath: IconTextField.stringValue, toPath: Utils().CDrivePath + "/Cache/PS1fPKG/sce_sys/pic0.png") + } catch (let error) { + print(error) + } + } + + // If config already exists, remove it + let ConfigDestinationPath: String = Utils().CDrivePath + "/Cache/PS1fPKG/config-title.txt" + if FileManager.default.fileExists(atPath: ConfigDestinationPath) + { + do { + try FileManager.default.removeItem(atPath: ConfigDestinationPath) + } catch (let error) { + print(error) + } + } + + // Create a new config file + FileManager.default.createFile(atPath: ConfigDestinationPath, contents:Data(" ".utf8), attributes: nil) + let ConfigWriter = FileHandle(forWritingAtPath: ConfigDestinationPath) + if ConfigWriter == nil { } + else + { + ConfigWriter!.write("--ps4-trophies=0\n".data(using: .utf8)!) + ConfigWriter!.write("--ps5-uds=0\n".data(using: .utf8)!) + ConfigWriter!.write("--trophies=0\n".data(using: .utf8)!) + + fPKGBuilderProgressIndicator.doubleValue += 10 + fPKGBuilderStatusTextField.stringValue = "Preparing selected disc(s)" + usleep(150000) + + // Discs Setup + Disc1CueFile = Disc1TextField.stringValue.replacingOccurrences(of: ".bin", with: ".cue") + ConfigWriter!.write(#"--image="data/disc1.bin"\#n"#.data(using: .utf8)!) + + // Copy selected discs to the inner Games folder (if not already exists) + let Disc1BINFileName: String = URL(string: Disc1TextField.stringValue)!.lastPathComponent + let Disc1CUEFileName: String = URL(string: Disc1CueFile)!.lastPathComponent + + if FileManager.default.fileExists(atPath: Disc1TextField.stringValue) && !FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Games/" + Disc1BINFileName) { + + do { + try FileManager.default.copyItem(atPath: Disc1TextField.stringValue, toPath: Utils().CDrivePath + "/Games/" + Disc1BINFileName) + try FileManager.default.copyItem(atPath: Disc1CueFile, toPath: Utils().CDrivePath + "/Games/" + Disc1CUEFileName) + } catch (let error) { + print(error) + } + } + + if !Disc2TextField.stringValue.isEmpty { + Disc2CueFile = Disc2TextField.stringValue.replacingOccurrences(of: ".bin", with: ".cue") + ConfigWriter!.write(#"--image="data/disc2.bin"\#n"#.data(using: .utf8)!) + + if FileManager.default.fileExists(atPath: Disc2TextField.stringValue) { + do { + let Disc2FileName: String = URL(string: Disc2TextField.stringValue)!.lastPathComponent + let Disc2CUEFileName: String = URL(string: Disc2CueFile)!.lastPathComponent + try FileManager.default.copyItem(atPath: Disc2TextField.stringValue, toPath: Utils().CDrivePath + "/Games/" + Disc2FileName) + try FileManager.default.copyItem(atPath: Disc2CueFile, toPath: Utils().CDrivePath + "/Games/" + Disc2CUEFileName) + } catch (let error) { + print(error) + } + } + } + if !Disc3TextField.stringValue.isEmpty { + Disc3CueFile = Disc3TextField.stringValue.replacingOccurrences(of: ".bin", with: ".cue") + ConfigWriter!.write(#"--image="data/disc3.bin"\#n"#.data(using: .utf8)!) + + if FileManager.default.fileExists(atPath: Disc3TextField.stringValue) { + do { + let Disc3FileName: String = URL(string: Disc3TextField.stringValue)!.lastPathComponent + let Disc3CUEFileName: String = URL(string: Disc3CueFile)!.lastPathComponent + try FileManager.default.copyItem(atPath: Disc3TextField.stringValue, toPath: Utils().CDrivePath + "/Games/" + Disc3FileName) + try FileManager.default.copyItem(atPath: Disc3CueFile, toPath: Utils().CDrivePath + "/Games/" + Disc3CUEFileName) + } catch (let error) { + print(error) + } + } + } + if !Disc4TextField.stringValue.isEmpty { + Disc4CueFile = Disc4TextField.stringValue.replacingOccurrences(of: ".bin", with: ".cue") + ConfigWriter!.write(#"--image="data/disc4.bin"\#n"#.data(using: .utf8)!) + + if FileManager.default.fileExists(atPath: Disc4TextField.stringValue) { + do { + let Disc4FileName: String = URL(string: Disc4TextField.stringValue)!.lastPathComponent + let Disc4CUEFileName: String = URL(string: Disc4CueFile)!.lastPathComponent + try FileManager.default.copyItem(atPath: Disc4TextField.stringValue, toPath: Utils().CDrivePath + "/Games/" + Disc4FileName) + try FileManager.default.copyItem(atPath: Disc4CueFile, toPath: Utils().CDrivePath + "/Games/" + Disc4CUEFileName) + } catch (let error) { + print(error) + } + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 + fPKGBuilderStatusTextField.stringValue = "Checking for game protection" + usleep(150000) + + // Check for game protection + var AdjustedGameID: String = CurrentGameID + AdjustedGameID.insert("_", at: AdjustedGameID.index(AdjustedGameID.startIndex, offsetBy: 4)) + AdjustedGameID.insert(".", at: AdjustedGameID.index(AdjustedGameID.startIndex, offsetBy: 8)) + var GameProtectionPatch: String = IsGameProtected(GameID: AdjustedGameID) + if !GameProtectionPatch.isEmpty { + let GameProtectionAlert = NSAlert() + GameProtectionAlert.messageText = "Please confirm" + GameProtectionAlert.informativeText = "This game is LibCrypt protected. Do you want to patch it automatically ?" + GameProtectionAlert.addButton(withTitle: "Yes") + GameProtectionAlert.addButton(withTitle: "No") + + fPKGBuilderStatusTextField.stringValue = "Waiting for user input" + + if GameProtectionAlert.runModal() == NSApplication.ModalResponse.alertFirstButtonReturn + { + GameProtectionPatch = "--libcrypt=" + GameProtectionPatch + "\n" + ConfigWriter!.write(GameProtectionPatch.data(using: .utf8)!) + } + } + + // LUA config + if !LUAConfigTextField.stringValue.isEmpty { + let LUATitleID: String = "--ps1-title-id=" + NPTitleTextField.stringValue + "\n" + ConfigWriter!.write(LUATitleID.data(using: .utf8)!) + } + + // Graphics config + let UpscaleValue: String = "--scale=" + UpscalingComboBox.stringValue + "\n" + ConfigWriter!.write(UpscaleValue.data(using: .utf8)!) + + if SkipBootlogoCheckBox.state == .on { + ConfigWriter!.write("--bios-hide-sce-osd=1\n".data(using: .utf8)!) + } + if EnableGunconCheckBox.state == .on { + ConfigWriter!.write("--guncon\n".data(using: .utf8)!) + } + if Force60HzCheckBox.state == .on { + ConfigWriter!.write("--gpu-scanout-fps-override=60\n".data(using: .utf8)!) + } + if EmulateAnalogSticksCheckBox.state == .on { + ConfigWriter!.write("--sim-analog-pad=0x2020\n".data(using: .utf8)!) + } + + // User config + if !TXTConfigTextField.stringValue.isEmpty { + ConfigWriter!.write("#User imported config".data(using: .utf8)!) + do { + let UserConfigData = try String(contentsOfFile: TXTConfigTextField.stringValue, encoding: .utf8) + if !UserConfigData.isEmpty { + ConfigWriter!.write(UserConfigData.data(using: .utf8)!) + } + } catch { + print(error) + } + } + + // Save config + ConfigWriter!.closeFile() + } + + fPKGBuilderProgressIndicator.doubleValue += 10 //50 + fPKGBuilderStatusTextField.stringValue = "PS1 emulator configuration done. Creating now the param.sfo file, please wait" + usleep(150000) + + // Create a new param.sfo file + CreateParamSFO(GameTitle: GameTitleTextField.stringValue) + if FileManager.default.fileExists(atPath: Utils().ToolsPath + "/param.sfo") { + + //Delete default param.sfo if exists + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/PS1fPKG/sce_sys/param.sfo") { + do { + try FileManager.default.removeItem(atPath: Utils().CDrivePath + "/Cache/PS1fPKG/sce_sys/param.sfo") + } catch (let error) { + print(error) + } + } + + // Move the new param.sfo to the sce_sys folder + do { + try FileManager.default.moveItem(atPath: Utils().ToolsPath + "/param.sfo", toPath: Utils().CDrivePath + "/Cache/PS1fPKG/sce_sys/param.sfo") + } catch (let error) { + print(error) + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 //60 + fPKGBuilderStatusTextField.stringValue = "Param.sfo file created. Creating disc(s) TOC" + usleep(150000) + + // Create a TOC file + CreateTOCFile() + + let CU2Path: String = Utils().ToolsPath + "/" + URL(fileURLWithPath: Disc1TextField.stringValue).deletingPathExtension().lastPathComponent + ".cu2" + let TOCPath: String = Utils().ToolsPath + "/" + URL(fileURLWithPath: Disc1TextField.stringValue).deletingPathExtension().lastPathComponent + ".TOC" + + if FileManager.default.fileExists(atPath: TOCPath) { + do { + try FileManager.default.moveItem(atPath: TOCPath, toPath: Utils().CDrivePath + "/Cache/PS1fPKG/data/disc1.toc") + try FileManager.default.removeItem(atPath: CU2Path) + } catch { + print(error) + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 //70 + fPKGBuilderStatusTextField.stringValue = "Disc(s) TOC created. Proceeding to GP4 project creation" + usleep(150000) + + // Generate a GP4 project and modify it + CreateGP4Project() + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/PS1fPKG.gp4") { + + let BaseFileNameWithExt: String = URL(fileURLWithPath: Disc1TextField.stringValue).lastPathComponent + let BaseFileName: String = BaseFileNameWithExt.replacingOccurrences(of: ".bin", with: "") + let Disc1CUEFilePath: String = "C:\\Games\\" + BaseFileName + ".cue" + let Disc1BINFilePath: String = "C:\\Games\\" + BaseFileName + ".bin" + + let XMLDisc1CuePath = "\n " + let XMLDisc1BinPath = "\n " + + do { + var fileContents = try String(contentsOfFile: Utils().CDrivePath + "/Cache/PS1fPKG.gp4", encoding: .utf8) + fileContents = fileContents.replacingOccurrences(of: "", with: "") + fileContents = fileContents.replacingOccurrences(of: "", with: "\(XMLDisc1CuePath)\(XMLDisc1BinPath)\n") + + try fileContents.write(toFile: Utils().CDrivePath + "/Cache/PS1fPKG.gp4", atomically: false, encoding: .utf8) + } catch { + print(error) + } + + if !Disc2TextField.stringValue.isEmpty { + let BaseFileNameWithExt: String = URL(fileURLWithPath: Disc2TextField.stringValue).lastPathComponent + let BaseFileName: String = BaseFileNameWithExt.replacingOccurrences(of: ".bin", with: "") + let Disc2CUEFilePath: String = "C:\\Games\\" + BaseFileName + ".cue" + let Disc2BINFilePath: String = "C:\\Games\\" + BaseFileName + ".bin" + + let XMLDisc2CuePath = "\n " + let XMLDisc2BinPath = "\n " + + do { + var fileContents = try String(contentsOfFile: Utils().CDrivePath + "/Cache/PS1fPKG.gp4", encoding: .utf8) + fileContents = fileContents.replacingOccurrences(of: "", with: "\(XMLDisc2CuePath)\(XMLDisc2BinPath)\n") + try fileContents.write(toFile: Utils().CDrivePath + "/Cache/PS1fPKG.gp4", atomically: true, encoding: .utf8) + } catch { + print(error) + } + } + + if !Disc3TextField.stringValue.isEmpty { + let BaseFileNameWithExt: String = URL(fileURLWithPath: Disc3TextField.stringValue).lastPathComponent + let BaseFileName: String = BaseFileNameWithExt.replacingOccurrences(of: ".bin", with: "") + let Disc3CUEFilePath: String = "C:\\Games\\" + BaseFileName + ".cue" + let Disc3BINFilePath: String = "C:\\Games\\" + BaseFileName + ".bin" + + let XMLDisc3CuePath = "\n " + let XMLDisc3BinPath = "\n " + + do { + var fileContents = try String(contentsOfFile: Utils().CDrivePath + "/Cache/PS1fPKG.gp4", encoding: .utf8) + fileContents = fileContents.replacingOccurrences(of: "", with: "\(XMLDisc3CuePath)\(XMLDisc3BinPath)\n") + try fileContents.write(toFile: Utils().CDrivePath + "/Cache/PS1fPKG.gp4", atomically: true, encoding: .utf8) + } catch { + print(error) + } + } + + if !Disc4TextField.stringValue.isEmpty { + let BaseFileNameWithExt: String = URL(fileURLWithPath: Disc4TextField.stringValue).lastPathComponent + let BaseFileName: String = BaseFileNameWithExt.replacingOccurrences(of: ".bin", with: "") + let Disc4CUEFilePath: String = "C:\\Games\\" + BaseFileName + ".cue" + let Disc4BINFilePath: String = "C:\\Games\\" + BaseFileName + ".bin" + + let XMLDisc4CuePath = "\n " + let XMLDisc4BinPath = "\n " + + do { + var fileContents = try String(contentsOfFile: Utils().CDrivePath + "/Cache/PS1fPKG.gp4", encoding: .utf8) + fileContents = fileContents.replacingOccurrences(of: "", with: "\(XMLDisc4CuePath)\(XMLDisc4BinPath)\n") + try fileContents.write(toFile: Utils().CDrivePath + "/Cache/PS1fPKG.gp4", atomically: true, encoding: .utf8) + } catch { + print(error) + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 //80 + fPKGBuilderStatusTextField.stringValue = "GP4 project created. fPKG will be built after user confirmation" + usleep(100000) + + let PKGReadyAlert = NSAlert() + PKGReadyAlert.messageText = "Files are ready for fPKG creation." + PKGReadyAlert.informativeText = "fPKG Ready" + PKGReadyAlert.addButton(withTitle: "Continue") + PKGReadyAlert.runModal() + + fPKGBuilderProgressIndicator.isIndeterminate = true + fPKGBuilderProgressIndicator.startAnimation(nil) + fPKGBuilderStatusTextField.stringValue = "The fPKG is building, please wait until done" + usleep(150000) + + // Create the fPKG + BuildPKG() + + // Check build + let PKGFileName: String = "UP9000-" + CurrentGameID + "_00-" + CurrentGameID + "PS1FPKG-A0100-V0100.pkg" + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/" + PKGFileName) { + + // Move the created fPKG into the selected output folder + do { + try FileManager.default.moveItem(atPath: Utils().CDrivePath + "/Cache/" + PKGFileName, toPath: PKGOutputPath + "/" + PKGFileName) + } catch (let error) { + print(error) + } + + fPKGBuilderProgressIndicator.stopAnimation(nil) + fPKGBuilderProgressIndicator.isIndeterminate = false + fPKGBuilderProgressIndicator.doubleValue = 100 //100 + fPKGBuilderSpinningIndicator.stopAnimation(nil) + fPKGBuilderSpinningIndicator.isHidden = true + fPKGBuilderStatusTextField.stringValue = "Done" + + let SuccessAlert = NSAlert() + SuccessAlert.messageText = "fPKG created with success!" + SuccessAlert.informativeText = "Done" + SuccessAlert.addButton(withTitle: "Continue") + SuccessAlert.runModal() + + } + } + + } + + func FindGameTitle(GameID: String) -> String { + if !GameID.isEmpty { + do { + let PS1DBContent = try String(contentsOfFile: Utils().CDrivePath + "/PS4/ps1ids.txt", encoding: .utf8) + let GameIDs = PS1DBContent.components(separatedBy: .newlines) + + var FoundTitle: String = "" + for FoundGameID in GameIDs { + if FoundGameID.contains(GameID) { + FoundTitle = FoundGameID.components(separatedBy: ";")[1] + break + } + } + + if FoundTitle.isEmpty { return "" } + else { return FoundTitle } + + } catch { + print(error) + return "" + } + + } + else { + return "" + } + } + + func IsGameProtected(GameID: String) -> String { + if !GameID.isEmpty { + do { + let LibCrData = try String(contentsOfFile: Utils().CDrivePath + "/PS4/libcrypt.txt", encoding: .utf8) + let GameIDs = LibCrData.components(separatedBy: .newlines) + + var FoundPatch: String = "" + for FoundGameID in GameIDs { + if FoundGameID.contains(GameID) { + FoundPatch = FoundGameID.components(separatedBy: " ")[1] + break + } + } + + if FoundPatch.isEmpty { return "" } + else { return FoundPatch } + + } catch { + print(error) + return "" + } + + } + else { + return "" + } + } + + func CreateParamSFO(GameTitle: String) { + let SFOUtil = Bundle.main.path(forResource: "sfoutil", ofType: "") + let ContentID: String = "UP9000-" + CurrentGameID + "_00-" + CurrentGameID + "PS1FPKG" + let DestinationPath: String = "'" + Utils().ToolsPath + "/param.sfo'" + let task = Process() + + task.launchPath = "/bin/sh" + task.arguments = ["-c", "'" + SFOUtil! + "' --force --new-file " + DestinationPath + " --add int APP_TYPE 1 --add str APP_VER \"01.00\" --add int ATTRIBUTE 0 --add str CATEGORY \"gd\" --add str CONTENT_ID \"\(ContentID)\" --add int DOWNLOAD_DATA_SIZE 0 --add str FORMAT \"obs\" --add int PARENTAL_LEVEL 5 --add int SYSTEM_VER 0 --add str TITLE \"" + GameTitle + "\" --add str TITLE_ID \"\(NPTitleTextField.stringValue)\" --add str VERSION \"01.00\""] + + task.launch() + task.waitUntilExit() + } + + func CreateTOCFile() { + let task = Process() + + let GameBINFileName: String = Disc1CueFile.replacingOccurrences(of: ".cue", with: ".bin") + var GameBINFileSize: String = "" + if let GameFileAttributes = try? FileManager.default.attributesOfItem(atPath: GameBINFileName) { + if let GameFileSizeAsBytes = GameFileAttributes[.size] as? Int64 { + let NewByteCountFormatter = ByteCountFormatter() + NewByteCountFormatter.allowedUnits = [.useBytes] + NewByteCountFormatter.countStyle = .file + NewByteCountFormatter.includesUnit = false + NewByteCountFormatter.zeroPadsFractionDigits = true + let FileSizeInBytes = NewByteCountFormatter.string(fromByteCount: GameFileSizeAsBytes) + GameBINFileSize = FileSizeInBytes.replacingOccurrences(of: ".", with: "") + } + } + + task.launchPath = "/bin/sh" + task.currentDirectoryPath = Utils().ToolsPath + task.arguments = ["-c", "'" + Utils().ToolsPath + "/cue2toc' '" + Disc1CueFile + "' --size " + GameBINFileSize] + + task.launch() + task.waitUntilExit() + } + + func CreateGP4Project() { + let task = Process() + + task.launchPath = "/bin/sh" + task.arguments = ["-c", "'" + Utils().ToolsPath + "/gengp4_ps1'"] + task.launch() + task.waitUntilExit() + } + + func BuildPKG() { + let task = Process() + + task.launchPath = "/bin/sh" + task.arguments = ["-c", "'" + Utils().ToolsPath + "/make_fPKG_PS1'"] + task.launch() + task.waitUntilExit() + } + +} diff --git a/macOS/fPKG Builder/PS2ClassicsViewController.swift b/macOS/fPKG Builder/PS2ClassicsViewController.swift new file mode 100644 index 0000000..8455c93 --- /dev/null +++ b/macOS/fPKG Builder/PS2ClassicsViewController.swift @@ -0,0 +1,838 @@ +// +// PS2ClassicsViewController.swift +// PS Mac Tools +// +// Created by SvenGDK on 14/08/2024. +// + +import Foundation +import AppKit + +class PS2ClassicsViewController: NSViewController { + + var CurrentGameID: String = "" + var CurrentGameCRC: String = "" + + override func viewDidLoad() { + super.viewDidLoad() + + // Set default values + EmulatorsComboBox.selectItem(at: 0) + MultitapComboBox.selectItem(at: 0) + UpscalingComboBox.selectItem(at: 0) + UprenderComboBox.selectItem(at: 1) + DisplayModeComboBox.selectItem(at: 0) + ShaderComboBox.selectItem(at: 0) + } + + override var representedObject: Any? { + didSet { + + } + } + + @IBOutlet weak var BuildfPKGButton: NSButton! + @IBOutlet weak var fPKGBuilderProgressIndicator: NSProgressIndicator! + @IBOutlet weak var fPKGBuilderSpinningIndicator: NSProgressIndicator! + @IBOutlet weak var fPKGBuilderStatusTextField: NSTextField! + + + @IBOutlet weak var GameTitleTextField: NSTextField! + @IBOutlet weak var NPTitleTextField: NSTextField! + @IBOutlet weak var IconTextField: NSTextField! + @IBOutlet weak var BackgroundTextField: NSTextField! + @IBOutlet weak var Disc1TextField: NSTextField! + @IBOutlet weak var Disc2TextField: NSTextField! + @IBOutlet weak var Disc3TextField: NSTextField! + @IBOutlet weak var Disc4TextField: NSTextField! + @IBOutlet weak var Disc5TextField: NSTextField! + @IBOutlet weak var TXTConfigTextField: NSTextField! + @IBOutlet weak var LUAConfigTextField: NSTextField! + @IBOutlet weak var UpscalingComboBox: NSComboBox! + @IBOutlet weak var UprenderComboBox: NSComboBox! + @IBOutlet weak var ShaderComboBox: NSComboBox! + @IBOutlet weak var DisplayModeComboBox: NSComboBox! + @IBOutlet weak var FixGraphicsCheckBox: NSButton! + @IBOutlet weak var ImproveSpeedCheckBox: NSButton! + @IBOutlet weak var DisableMTVUCheckBox: NSButton! + @IBOutlet weak var DisableInstantVIF1TransferCheckBox: NSButton! + @IBOutlet weak var PS2MemoryCardTextField: NSTextField! + @IBOutlet weak var EmulatorsComboBox: NSComboBox! + @IBOutlet weak var MultitapComboBox: NSComboBox! + @IBOutlet weak var RestartEmuOnDiscChangeCheckBox: NSButton! + + @IBAction func BrowseIcon(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select an icon for your game" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().PNGType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + IconTextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseBackground(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select a background for your game" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().PNGType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + BackgroundTextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseDisc1(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select your PS2 ISO file" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().ISOType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + Disc1TextField.stringValue = result!.path + + // Read ISO file using isoinfo + let ISOReaderOutput = CheckPS2Game(ISOFile: result!.path).output + + // Get the game ID + let GameID = ISOReaderOutput.filter({(item: String) -> Bool in + let stringMatch = item.lowercased().range(of: "BOOT2 = ".lowercased()) + let stringMatch2 = item.lowercased().range(of: "BOOT2=".lowercased()) + if stringMatch == nil { + return stringMatch2 != nil ? true : false + } + else { + return stringMatch != nil ? true : false + } + }) + + // Get the game title if a game ID has been found + if !GameID.isEmpty { + if GameID.count > 0 { + CurrentGameID = FormatGameID(GameID: GameID[0].components(separatedBy: "=")[1]) + let GameTitle: String = FindGameTitle(GameID: CurrentGameID) + NPTitleTextField.stringValue = CurrentGameID + GameTitleTextField.stringValue = GameTitle + } + } + + BuildfPKGButton.isEnabled = true + } else { + return + } + } + + @IBAction func BrowseDisc2(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select the Disc 2 ISO file" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().ISOType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + Disc2TextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseDisc3(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select the Disc 3 ISO file" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().ISOType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + Disc3TextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseDisc4(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select the Disc 4 ISO file" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().ISOType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + Disc4TextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseDisc5(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select the Disc 5 ISO file" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().ISOType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + Disc5TextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseTXTConfig(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select a TXT config file" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().TXTType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + TXTConfigTextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseLUAConfig(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select a LUA config file" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().TXTType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + LUAConfigTextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseMemoryCard(_ sender: NSButton) { + + } + + func CheckPS2Game(ISOFile: String) -> (output: [String], error: [String], exitCode: Int32) { + var output : [String] = [] + var error : [String] = [] + + let isoutil = Bundle.main.path(forResource: "isoinfo", ofType: "") + let task = Process() + task.launchPath = "/bin/sh" + task.arguments = ["-c", "'" + isoutil! + "' -i '" + ISOFile + "' -x '/SYSTEM.CNF;1'"] + + let outpipe = Pipe() + task.standardOutput = outpipe + let errpipe = Pipe() + task.standardError = errpipe + + task.launch() + + let outdata = outpipe.fileHandleForReading.readDataToEndOfFile() + if var string = String(data: outdata, encoding: .utf8) { + string = string.trimmingCharacters(in: .newlines) + output = string.components(separatedBy: "\n") + print(output) + } + + let errdata = errpipe.fileHandleForReading.readDataToEndOfFile() + if var string = String(data: errdata, encoding: .utf8) { + string = string.trimmingCharacters(in: .newlines) + error = string.components(separatedBy: "\n") + print(error) + } + + task.waitUntilExit() + let status = task.terminationStatus + return (output, error, status) + } + + func FindGameTitle(GameID: String) -> String { + if !GameID.isEmpty { + do { + let PS2DBContent = try String(contentsOfFile: Utils().CDrivePath + "/PS4/ps2ids.txt", encoding: .utf8) + let GameIDs = PS2DBContent.components(separatedBy: .newlines) + + var FoundTitle: String = "" + for FoundGameID in GameIDs { + if FoundGameID.contains(GameID) { + FoundTitle = FoundGameID.components(separatedBy: ";")[1] + break + } + } + + if FoundTitle.isEmpty { return "" } + else { return FoundTitle } + + } catch { + print(error) + return "" + } + + } + else { + return "" + } + } + + func FormatGameID(GameID: String) -> String { + return GameID.components(separatedBy: ":\\")[1].replacingOccurrences(of: ".", with: "", options: NSString.CompareOptions.literal, range: nil).replacingOccurrences(of: "_", with: "", options: NSString.CompareOptions.literal, range: nil).replacingOccurrences(of: ";1", with: "", options: NSString.CompareOptions.literal, range: nil) + } + + @IBAction func BuildfPKG(_ sender: NSButton) { + + let MissingInfoAlert = NSAlert() + MissingInfoAlert.addButton(withTitle: "Close") + + if Disc1TextField.stringValue.isEmpty { + MissingInfoAlert.messageText = "Disc 1 is missing, cannot continue." + MissingInfoAlert.runModal() + return + } + + if GameTitleTextField.stringValue.isEmpty { + MissingInfoAlert.messageText = "The Game Title is missing, cannot continue." + MissingInfoAlert.runModal() + return + } + + if NPTitleTextField.stringValue.isEmpty { + MissingInfoAlert.messageText = "The NP Title is missing, cannot continue." + MissingInfoAlert.runModal() + return + } + + fPKGBuilderProgressIndicator.isHidden = false + fPKGBuilderSpinningIndicator.isHidden = false + fPKGBuilderStatusTextField.isHidden = false + + fPKGBuilderProgressIndicator.isIndeterminate = true + fPKGBuilderProgressIndicator.startAnimation(nil) + fPKGBuilderSpinningIndicator.startAnimation(nil) + fPKGBuilderStatusTextField.stringValue = "Waiting for user input" + + CreatefPKG() + } + + func CreatefPKG() { + + var PKGOutputPath: String = "" + let FolderBrowserOpenPanel = NSOpenPanel() + + FolderBrowserOpenPanel.title = "Select an output folder" + FolderBrowserOpenPanel.showsResizeIndicator = true + FolderBrowserOpenPanel.showsHiddenFiles = false + FolderBrowserOpenPanel.canChooseFiles = false + FolderBrowserOpenPanel.canChooseDirectories = true + FolderBrowserOpenPanel.allowsMultipleSelection = false + + // Set destination folder where the pkg file will be moved into + if (FolderBrowserOpenPanel.runModal() == NSApplication.ModalResponse.OK) { + let result = FolderBrowserOpenPanel.url + PKGOutputPath = result!.path + } else { + return + } + + // Set disc count + var DiscCount: Int = 1 + if !Disc2TextField.stringValue.isEmpty { + DiscCount += 1 + } + if !Disc3TextField.stringValue.isEmpty { + DiscCount += 1 + } + if !Disc4TextField.stringValue.isEmpty { + DiscCount += 1 + } + if !Disc5TextField.stringValue.isEmpty { + DiscCount += 1 + } + + fPKGBuilderProgressIndicator.stopAnimation(nil) + fPKGBuilderProgressIndicator.isIndeterminate = false + fPKGBuilderProgressIndicator.doubleValue = 0 + fPKGBuilderStatusTextField.stringValue = "Removing old fPKG build folder" + usleep(150000) + + // Remove previous fPKG GP4 project + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/PS2fPKG.gp4") { + do { + try FileManager.default.removeItem(atPath: Utils().CDrivePath + "/Cache/PS2fPKG.gp4") + } catch (let error) { + print(error) + } + } + + // Remove previous fPKG project folder + var isDir: ObjCBool = true + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/PS2fPKG", isDirectory: &isDir) { + do { + try FileManager.default.removeItem(atPath: Utils().CDrivePath + "/Cache/PS2fPKG") + } catch (let error) { + print(error) + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 + fPKGBuilderStatusTextField.stringValue = "Preparing the PS2 emulator" + usleep(150000) + + // Copy the selected PS2 emulator to the new project folder + if EmulatorsComboBox.stringValue == "Jak v2" { + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/PS4/emus/Jakv2", isDirectory: &isDir) { + do { + try FileManager.default.copyItem(atPath: Utils().CDrivePath + "/PS4/emus/Jakv2", toPath: Utils().CDrivePath + "/Cache/PS2fPKG") + } catch (let error) { + print(error) + } + } + } + else if EmulatorsComboBox.stringValue == "Rogue v1" { + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/PS4/emus/Roguev1", isDirectory: &isDir) { + do { + try FileManager.default.copyItem(atPath: Utils().CDrivePath + "/PS4/emus/Roguev1", toPath: Utils().CDrivePath + "/Cache/PS2fPKG") + } catch (let error) { + print(error) + } + } + } + + // Copy the selected icon to the project folder + if !IconTextField.stringValue.isEmpty { + do { + try FileManager.default.copyItem(atPath: IconTextField.stringValue, toPath: Utils().CDrivePath + "/Cache/PS2fPKG/sce_sys/icon0.png") + } catch (let error) { + print(error) + } + } + + // Copy the selected background to the project folder + if !BackgroundTextField.stringValue.isEmpty { + do { + try FileManager.default.copyItem(atPath: IconTextField.stringValue, toPath: Utils().CDrivePath + "/Cache/PS2fPKG/sce_sys/pic0.png") + } catch (let error) { + print(error) + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 //20 + fPKGBuilderStatusTextField.stringValue = "Creating param.sfo file, please wait" + usleep(150000) + + // Create a new param.sfo file + CreateParamSFO(GameTitle: GameTitleTextField.stringValue) + if FileManager.default.fileExists(atPath: Utils().ToolsPath + "/param.sfo") { + + //Delete default param.sfo if exists + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/PS2fPKG/sce_sys/param.sfo") { + do { + try FileManager.default.removeItem(atPath: Utils().CDrivePath + "/Cache/PS2fPKG/sce_sys/param.sfo") + } catch (let error) { + print(error) + } + } + + // Move the new param.sfo to the sce_sys folder + do { + try FileManager.default.moveItem(atPath: Utils().ToolsPath + "/param.sfo", toPath: Utils().CDrivePath + "/Cache/PS2fPKG/sce_sys/param.sfo") + } catch (let error) { + print(error) + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 //30 + fPKGBuilderStatusTextField.stringValue = "Configurating PS2 Emulator, please wait" + usleep(150000) + + // If config already exists, remove it + let ConfigDestinationPath: String = Utils().CDrivePath + "/Cache/PS2fPKG/config-emu-ps4.txt" + if FileManager.default.fileExists(atPath: ConfigDestinationPath) + { + do { + try FileManager.default.removeItem(atPath: ConfigDestinationPath) + } catch (let error) { + print(error) + } + } + + // Create a new config file + FileManager.default.createFile(atPath: ConfigDestinationPath, contents:Data(" ".utf8), attributes: nil) + let ConfigWriter = FileHandle(forWritingAtPath: ConfigDestinationPath) + if ConfigWriter == nil { } + else + { + // Required config + let NewPS2EmulatorConfig: [String] = [ + "--path-vmc=\"/tmp/vmc\"\n", + "--config-local-lua=\"\"\n", + "--ps2-title-id=" + CurrentGameID, + "\n--max-disc-num=" + String(DiscCount), + "\n--gs-uprender=" + UprenderComboBox.stringValue.lowercased(), + "\n--gs-upscale=" + UpscalingComboBox.stringValue.lowercased(), + "\n--host-audio=1\n", + "--rom=\"PS20220WD20050620.crack\"\n", + "--verbose-cdvd-reads=0\n", + "--host-display-mode=" + DisplayModeComboBox.stringValue.lowercased() + ] + let NewPS2EmulatorConfigContent: String = NewPS2EmulatorConfig.joined() + ConfigWriter!.write(NewPS2EmulatorConfigContent.data(using: .utf8)!) + + // Restart emulator on disc change config + if RestartEmuOnDiscChangeCheckBox.state == .on { + ConfigWriter!.write("#Disable emu reset on disc change".data(using: .utf8)!) + ConfigWriter!.write("--switch-disc-reset=0".data(using: .utf8)!) + } + + // Multitap config + if MultitapComboBox.indexOfSelectedItem == 1 { + ConfigWriter!.write("#Enable Multitap on port 1".data(using: .utf8)!) + ConfigWriter!.write("--mtap1=always".data(using: .utf8)!) + } + else if MultitapComboBox.indexOfSelectedItem == 2 { + ConfigWriter!.write("#Enable Multitap on port 2".data(using: .utf8)!) + ConfigWriter!.write("--mtap2=always".data(using: .utf8)!) + } + else if MultitapComboBox.indexOfSelectedItem == 3 { + ConfigWriter!.write("#Enable Multitap on both ports".data(using: .utf8)!) + ConfigWriter!.write("--mtap1=always".data(using: .utf8)!) + ConfigWriter!.write("--mtap2=always".data(using: .utf8)!) + } + + // Emulator fixes + if ImproveSpeedCheckBox.state == .on { + ConfigWriter!.write("#Improve Speed".data(using: .utf8)!) + ConfigWriter!.write("-vu0-opt-flags=1".data(using: .utf8)!) + ConfigWriter!.write("--vu1-opt-flags=1".data(using: .utf8)!) + ConfigWriter!.write("--cop2-opt-flags=1".data(using: .utf8)!) + ConfigWriter!.write("--vu0-const-prop=0".data(using: .utf8)!) + ConfigWriter!.write("--vu1-const-prop=0".data(using: .utf8)!) + ConfigWriter!.write("--vu1-jr-cache-policy=newprog".data(using: .utf8)!) + ConfigWriter!.write("--vu1-jalr-cache-policy=newprog".data(using: .utf8)!) + ConfigWriter!.write("--vu0-jr-cache-policy=newprog".data(using: .utf8)!) + ConfigWriter!.write("--vu0-jalr-cache-policy=newprog".data(using: .utf8)!) + } + if FixGraphicsCheckBox.state == .on { + ConfigWriter!.write("#Fix Graphics".data(using: .utf8)!) + ConfigWriter!.write("--fpu-no-clamping=0".data(using: .utf8)!) + ConfigWriter!.write("--fpu-clamp-results=1".data(using: .utf8)!) + ConfigWriter!.write("--vu0-no-clamping=0".data(using: .utf8)!) + ConfigWriter!.write("--vu0-clamp-results=1".data(using: .utf8)!) + ConfigWriter!.write("--vu1-no-clamping=0".data(using: .utf8)!) + ConfigWriter!.write("--vu1-clamp-results=1".data(using: .utf8)!) + ConfigWriter!.write("--cop2-no-clamping=0".data(using: .utf8)!) + ConfigWriter!.write("--cop2-clamp-results=1".data(using: .utf8)!) + } + if DisableMTVUCheckBox.state == .on { + ConfigWriter!.write("#Disable MTVU".data(using: .utf8)!) + ConfigWriter!.write("--vu1=jit-sync".data(using: .utf8)!) + } + if DisableInstantVIF1TransferCheckBox.state == .on { + ConfigWriter!.write("#Disable Instant VIF1 Transfer".data(using: .utf8)!) + ConfigWriter!.write("--vif1-instant-xfer=0".data(using: .utf8)!) + } + + ConfigWriter!.closeFile() + } + + // Append custom config + if !TXTConfigTextField.stringValue.isEmpty { + let AdditionalConfig = try? String(contentsOfFile: TXTConfigTextField.stringValue, encoding: .utf8) + if let NewFileHandle = FileHandle(forWritingAtPath: ConfigDestinationPath) { + NewFileHandle.seekToEndOfFile() + if let DataToWrite = AdditionalConfig!.data(using: .utf8) { + NewFileHandle.write("#User Config".data(using: .utf8)!) + NewFileHandle.write(DataToWrite) + } + NewFileHandle.closeFile() + } + } + + // Add Memory Card if selected + if !PS2MemoryCardTextField.stringValue.isEmpty { + + var AdjustedGameID: String = CurrentGameID + AdjustedGameID.insert("-", at: AdjustedGameID.index(AdjustedGameID.startIndex, offsetBy: 4)) + + // Create feature_data folder + if !FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/PS2fPKG/feature_data", isDirectory: &isDir) { + do { + try FileManager.default.createDirectory(atPath: Utils().CDrivePath + "/Cache/PS2fPKG/feature_data", withIntermediateDirectories: false) + try FileManager.default.createDirectory(atPath: Utils().CDrivePath + "/Cache/PS2fPKG/feature_data/" + AdjustedGameID, withIntermediateDirectories: false) + } catch (let error) { + print(error) + } + } + + // Copy Memory Card to feature_data + if FileManager.default.fileExists(atPath: PS2MemoryCardTextField.stringValue) { + do { + try FileManager.default.copyItem(atPath: PS2MemoryCardTextField.stringValue, toPath: Utils().CDrivePath + "/Cache/PS2fPKG/feature_data/" + AdjustedGameID + "/custom.card") + } catch (let error) { + print(error) + } + } + + } + + if !FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/PS2fPKG/image", isDirectory: &isDir) { + do { + try FileManager.default.createDirectory(atPath: Utils().CDrivePath + "/Cache/PS2fPKG/image", withIntermediateDirectories: false) + } catch (let error) { + print(error) + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 //40 + fPKGBuilderStatusTextField.stringValue = "PS2 emulator configuration done. Creating now the GP4 project" + usleep(150000) + + // Generate a GP4 project and modify it + CreateGP4Project() + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/PS2fPKG.gp4") { + + fPKGBuilderProgressIndicator.doubleValue += 20 //60 + fPKGBuilderStatusTextField.stringValue = "GP4 project created. Preparing disc(s)" + usleep(100000) + + var FullDiscInfo: String = "" + + // Copy selected discs to the inner Games folder (if not already exists) & add to GP4 project file + let Disc1BaseFileNameWithExt: String = URL(fileURLWithPath: Disc1TextField.stringValue).lastPathComponent + let Disc1BaseFileName: String = Disc1BaseFileNameWithExt.replacingOccurrences(of: ".iso", with: "") + let Disc1ISOFilePath: String = "C:\\Games\\" + Disc1BaseFileName + ".iso" + let Disc1ISOFileName: String = URL(string: Disc1TextField.stringValue)!.lastPathComponent + if FileManager.default.fileExists(atPath: Disc1TextField.stringValue) && !FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Games/" + Disc1ISOFileName) { + do { + try FileManager.default.copyItem(atPath: Disc1TextField.stringValue, toPath: Utils().CDrivePath + "/Games/" + Disc1ISOFileName) + let NewDiscInfo = [FullDiscInfo, "\n "] + FullDiscInfo = NewDiscInfo.joined() + } catch (let error) { + print(error) + } + } + if !Disc2TextField.stringValue.isEmpty { + let Disc2BaseFileNameWithExt: String = URL(fileURLWithPath: Disc2TextField.stringValue).lastPathComponent + let Disc2BaseFileName: String = Disc2BaseFileNameWithExt.replacingOccurrences(of: ".iso", with: "") + let Disc2ISOFilePath: String = "C:\\Games\\" + Disc2BaseFileName + ".iso" + let Disc2ISOFileName: String = URL(string: Disc2TextField.stringValue)!.lastPathComponent + if FileManager.default.fileExists(atPath: Disc2TextField.stringValue) && !FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Games/" + Disc2ISOFileName) { + do { + try FileManager.default.copyItem(atPath: Disc2TextField.stringValue, toPath: Utils().CDrivePath + "/Games/" + Disc2ISOFileName) + let NewDiscInfo = [FullDiscInfo, "\n "] + FullDiscInfo = NewDiscInfo.joined() + } catch (let error) { + print(error) + } + } + } + if !Disc3TextField.stringValue.isEmpty { + let Disc3BaseFileNameWithExt: String = URL(fileURLWithPath: Disc3TextField.stringValue).lastPathComponent + let Disc3BaseFileName: String = Disc3BaseFileNameWithExt.replacingOccurrences(of: ".iso", with: "") + let Disc3ISOFilePath: String = "C:\\Games\\" + Disc3BaseFileName + ".iso" + let Disc3ISOFileName: String = URL(string: Disc3TextField.stringValue)!.lastPathComponent + if FileManager.default.fileExists(atPath: Disc3TextField.stringValue) && !FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Games/" + Disc3ISOFileName) { + do { + try FileManager.default.copyItem(atPath: Disc3TextField.stringValue, toPath: Utils().CDrivePath + "/Games/" + Disc3ISOFileName) + let NewDiscInfo = [FullDiscInfo, "\n "] + FullDiscInfo = NewDiscInfo.joined() + } catch (let error) { + print(error) + } + } + } + if !Disc4TextField.stringValue.isEmpty { + let Disc4BaseFileNameWithExt: String = URL(fileURLWithPath: Disc4TextField.stringValue).lastPathComponent + let Disc4BaseFileName: String = Disc4BaseFileNameWithExt.replacingOccurrences(of: ".iso", with: "") + let Disc4ISOFilePath: String = "C:\\Games\\" + Disc4BaseFileName + ".iso" + let Disc4ISOFileName: String = URL(string: Disc4TextField.stringValue)!.lastPathComponent + if FileManager.default.fileExists(atPath: Disc4TextField.stringValue) && !FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Games/" + Disc4ISOFileName) { + do { + try FileManager.default.copyItem(atPath: Disc4TextField.stringValue, toPath: Utils().CDrivePath + "/Games/" + Disc4ISOFileName) + let NewDiscInfo = [FullDiscInfo, "\n "] + FullDiscInfo = NewDiscInfo.joined() + } catch (let error) { + print(error) + } + } + } + if !Disc5TextField.stringValue.isEmpty { + let Disc5BaseFileNameWithExt: String = URL(fileURLWithPath: Disc5TextField.stringValue).lastPathComponent + let Disc5BaseFileName: String = Disc5BaseFileNameWithExt.replacingOccurrences(of: ".iso", with: "") + let Disc5ISOFilePath: String = "C:\\Games\\" + Disc5BaseFileName + ".iso" + let Disc5ISOFileName: String = URL(string: Disc5TextField.stringValue)!.lastPathComponent + if FileManager.default.fileExists(atPath: Disc5TextField.stringValue) && !FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Games/" + Disc5ISOFileName) { + do { + try FileManager.default.copyItem(atPath: Disc5TextField.stringValue, toPath: Utils().CDrivePath + "/Games/" + Disc5ISOFileName) + let NewDiscInfo = [FullDiscInfo, "\n "] + FullDiscInfo = NewDiscInfo.joined() + } catch (let error) { + print(error) + } + } + } + + // Modify the GP4 project + do { + var fileContents = try String(contentsOfFile: Utils().CDrivePath + "/Cache/PS2fPKG.gp4", encoding: .utf8) + fileContents = fileContents.replacingOccurrences(of: "", with: "") + fileContents = fileContents.replacingOccurrences(of: "", with: "\(FullDiscInfo)\n") + + try fileContents.write(toFile: Utils().CDrivePath + "/Cache/PS2fPKG.gp4", atomically: false, encoding: .utf8) + } catch { + print(error) + } + + fPKGBuilderProgressIndicator.doubleValue += 20 //80 + fPKGBuilderStatusTextField.stringValue = "Disc(s) copied & GP4 project modified. fPKG will be built after user confirmation" + usleep(100000) + + let PKGReadyAlert = NSAlert() + PKGReadyAlert.messageText = "Files are ready for fPKG creation." + PKGReadyAlert.informativeText = "fPKG Ready" + PKGReadyAlert.addButton(withTitle: "Continue") + PKGReadyAlert.runModal() + + fPKGBuilderProgressIndicator.isIndeterminate = true + fPKGBuilderProgressIndicator.startAnimation(nil) + fPKGBuilderStatusTextField.stringValue = "The fPKG is building, please wait until done" + usleep(150000) + + // Create the fPKG + BuildPKG() + + // Check build + let PKGFileName: String = "UP9000-" + CurrentGameID + "_00-" + CurrentGameID + "PS2FPKG-A0100-V0100.pkg" + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/" + PKGFileName) { + + // Move the created fPKG into the selected output folder + do { + try FileManager.default.moveItem(atPath: Utils().CDrivePath + "/Cache/" + PKGFileName, toPath: PKGOutputPath + "/" + PKGFileName) + } catch (let error) { + print(error) + } + + fPKGBuilderProgressIndicator.stopAnimation(nil) + fPKGBuilderProgressIndicator.isIndeterminate = false + fPKGBuilderProgressIndicator.doubleValue = 100 //100 + fPKGBuilderSpinningIndicator.stopAnimation(nil) + fPKGBuilderSpinningIndicator.isHidden = true + fPKGBuilderStatusTextField.stringValue = "Done" + + let SuccessAlert = NSAlert() + SuccessAlert.messageText = "fPKG created with success!" + SuccessAlert.informativeText = "Done" + SuccessAlert.addButton(withTitle: "Continue") + SuccessAlert.runModal() + + } + + } + + } + + func ExtractFileFromISO(ISOFile: String, FileToExtract: String) -> String { + // Extracts the specified file into the Tools directory + let unAr = Bundle.main.path(forResource: "unar", ofType: "") + let task = Process() + + task.launchPath = "/bin/sh" + task.arguments = ["-c", "'" + unAr! + "' -f -o '" + Utils().ToolsPath + "/' '" + ISOFile + "' " + FileToExtract] + task.launch() + task.waitUntilExit() + + let OutputFolderName: String = URL(string: ISOFile)!.deletingPathExtension().lastPathComponent + // Check if file has been extracted successfully + if FileManager.default.fileExists(atPath: Utils().ToolsPath + "/" + OutputFolderName + "/" + FileToExtract) { + return Utils().ToolsPath + "/" + OutputFolderName + "/" + FileToExtract + } + else + { + return "" + } + } + + func CreateParamSFO(GameTitle: String) { + let SFOUtil = Bundle.main.path(forResource: "sfoutil", ofType: "") + let ContentID: String = "UP9000-" + CurrentGameID + "_00-" + CurrentGameID + "PS2FPKG" + let DestinationPath: String = "'" + Utils().ToolsPath + "/param.sfo'" + let task = Process() + + task.launchPath = "/bin/sh" + task.arguments = ["-c", "'" + SFOUtil! + "' --force --new-file " + DestinationPath + " --add int APP_TYPE 1 --add str APP_VER \"01.00\" --add int ATTRIBUTE 0 --add str CATEGORY \"gd\" --add str CONTENT_ID \"\(ContentID)\" --add int DOWNLOAD_DATA_SIZE 0 --add str FORMAT \"obs\" --add int PARENTAL_LEVEL 5 --add int SYSTEM_VER 0 --add str TITLE \"" + GameTitle + "\" --add str TITLE_ID \"\(NPTitleTextField.stringValue)\" --add str VERSION \"01.00\""] + + task.launch() + task.waitUntilExit() + } + + func CreateGP4Project() { + let task = Process() + + task.launchPath = "/bin/sh" + task.arguments = ["-c", "'" + Utils().ToolsPath + "/gengp4_ps2'"] + task.launch() + task.waitUntilExit() + } + + func BuildPKG() { + let task = Process() + + task.launchPath = "/bin/sh" + task.arguments = ["-c", "'" + Utils().ToolsPath + "/make_fPKG_PS2'"] + task.launch() + task.waitUntilExit() + } + +} diff --git a/macOS/fPKG Builder/PSClassicsTabViewController.swift b/macOS/fPKG Builder/PSClassicsTabViewController.swift new file mode 100644 index 0000000..c984902 --- /dev/null +++ b/macOS/fPKG Builder/PSClassicsTabViewController.swift @@ -0,0 +1,22 @@ +// +// PSClassicsTabViewController.swift +// fPKG Builder +// +// Created by SvenGDK on 18/08/2024. +// + +import Foundation +import Cocoa + +class PSClassicsTabViewController: NSTabViewController { + + override func viewDidLoad() { + super.viewDidLoad() + } + + override var representedObject: Any? { + didSet { + } + } + +} diff --git a/macOS/fPKG Builder/PSClassicsWindowController.swift b/macOS/fPKG Builder/PSClassicsWindowController.swift new file mode 100644 index 0000000..6628bbd --- /dev/null +++ b/macOS/fPKG Builder/PSClassicsWindowController.swift @@ -0,0 +1,17 @@ +// +// PPPwnWindowController.swift +// PS Mac Tools +// +// Created by SvenGDK on 14/08/2024. +// + +import Foundation +import AppKit + +class PSClassicsWindowController: NSWindowController { + + override func windowDidLoad() { + super.windowDidLoad() + } + +} diff --git a/macOS/fPKG Builder/PSPClassicsViewController.swift b/macOS/fPKG Builder/PSPClassicsViewController.swift new file mode 100644 index 0000000..edbcc09 --- /dev/null +++ b/macOS/fPKG Builder/PSPClassicsViewController.swift @@ -0,0 +1,656 @@ +// +// PSPClassicsViewController.swift +// PS Mac Tools +// +// Created by SvenGDK on 14/08/2024. +// + +import Foundation +import AppKit + +class PSPClassicsViewController: NSViewController { + + var CurrentGameID: String = "" + + override func viewDidLoad() { + super.viewDidLoad() + } + + override var representedObject: Any? { + didSet { + } + } + + @IBOutlet weak var BuildfPKGButton: NSButton! + @IBOutlet weak var ProgressTextField: NSTextField! + @IBOutlet weak var fPKGBuilderProgressIndicator: NSProgressIndicator! + @IBOutlet weak var fPKGBuilderSpinningIndicator: NSProgressIndicator! + @IBOutlet weak var fPKGBuilderStatusTextField: NSTextField! + + @IBOutlet weak var GameTitleTextField: NSTextField! + @IBOutlet weak var NPTitleTextField: NSTextField! + @IBOutlet weak var IconTextField: NSTextField! + @IBOutlet weak var BackgroundTextField: NSTextField! + @IBOutlet weak var GameISOTextField: NSTextField! + @IBOutlet weak var CustomConfigTextField: NSTextField! + + @IBAction func BrowseIcon(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select an icon for your game" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().PNGType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + IconTextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseBackground(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select a background for your game" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().PNGType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + BackgroundTextField.stringValue = result!.path + } else { + return + } + } + + @IBAction func BrowseGameISO(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select your PSP ISO file" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().ISOType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + let FilePath: String = result!.path + + // Check if game is a PSP ISO (error output needs to be checked here) + let ISOReaderOutput = CheckPSPGame(ISOFile: FilePath).error + if !ISOReaderOutput.isEmpty { + + // Extract UMD_DATA.BIN + let ExtractedUMDDataPath: String = ExtractFileFromISO(ISOFile: FilePath, FileToExtract: "UMD_DATA.BIN") + + // Read UMD_DATA.BIN + if FileManager.default.fileExists(atPath: ExtractedUMDDataPath) { + + let UMDData = ReadUMDData(FileToRead: ExtractedUMDDataPath, Offset: 0, Length: 10) + let NPTitle = String(bytes: UMDData!, encoding: .ascii)?.replacingOccurrences(of: "-", with: "") ?? "" + let GameTitle = result!.deletingPathExtension().lastPathComponent.replacingOccurrences(of: "(.*?)", with: "", options: .regularExpression).replacingOccurrences(of: " {2,}", with: "", options: .regularExpression).replacingOccurrences(of: "_", with: " ") + + NPTitleTextField.stringValue = NPTitle + GameTitleTextField.stringValue = GameTitle + CurrentGameID = NPTitle + } + + GameISOTextField.stringValue = result!.path + BuildfPKGButton.isEnabled = true + } + else + { + // No valid PSP ISO + } + + } else { + return + } + } + + @IBAction func BrowseCustomConfig(_ sender: NSButton) { + let dialog = NSOpenPanel() + + dialog.title = "Select a custom config file" + dialog.showsResizeIndicator = true + dialog.showsHiddenFiles = false + dialog.canChooseFiles = true + dialog.canChooseDirectories = false + dialog.allowsMultipleSelection = false + dialog.allowedContentTypes = [Utils().TXTType] + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + CustomConfigTextField.stringValue = result!.path + } else { + return + } + } + + func CheckPSPGame(ISOFile: String) -> (output: [String], error: [String], exitCode: Int32) { + var output : [String] = [] + var error : [String] = [] + + let isoutil = Bundle.main.path(forResource: "isoinfo", ofType: "") + let task = Process() + task.launchPath = "/bin/sh" + task.arguments = ["-c", "'" + isoutil! + "' -i '" + ISOFile + "' -x 'PSP_GAME/PARAM.SFO'"] + + let outpipe = Pipe() + task.standardOutput = outpipe + let errpipe = Pipe() + task.standardError = errpipe + + task.launch() + + let outdata = outpipe.fileHandleForReading.readDataToEndOfFile() + if var string = String(data: outdata, encoding: .utf8) { + string = string.trimmingCharacters(in: .newlines) + output = string.components(separatedBy: "\n") + print(output) + } + + let errdata = errpipe.fileHandleForReading.readDataToEndOfFile() + if var string = String(data: errdata, encoding: .utf8) { + string = string.trimmingCharacters(in: .newlines) + error = string.components(separatedBy: "\n") + print(error) + } + + task.waitUntilExit() + let status = task.terminationStatus + return (output, error, status) + } + + func ExtractFileFromISO(ISOFile: String, FileToExtract: String) -> String { + // Extracts the specified file into the Tools directory + let unAr = Bundle.main.path(forResource: "unar", ofType: "") + let task = Process() + + task.launchPath = "/bin/sh" + task.arguments = ["-c", "'" + unAr! + "' -f -o '" + Utils().ToolsPath + "/' '" + ISOFile + "' " + FileToExtract] + task.launch() + task.waitUntilExit() + + let OutputFolderName: String = URL(string: ISOFile)!.deletingPathExtension().lastPathComponent + // Check if file has been extracted successfully + if FileManager.default.fileExists(atPath: Utils().ToolsPath + "/" + OutputFolderName + "/" + FileToExtract) { + return Utils().ToolsPath + "/" + OutputFolderName + "/" + FileToExtract + } + else + { + return "" + } + } + + func ReadUMDData(FileToRead: String, Offset: Int64, Length: Int) -> [UInt8]? { + var offset: Int64 = Offset + var newByte = [UInt8](repeating: 0, count: Length) + if let fileHandle = FileHandle(forReadingAtPath: FileToRead) { + let baseStreamLength = fileHandle.seekToEndOfFile() + fileHandle.seek(toFileOffset: UInt64(offset)) + var num = 0 + + while offset < baseStreamLength && num < Length { + let byte = fileHandle.readData(ofLength: 1) + if let byteValue = byte.first { + newByte[num] = byteValue + offset += 1 + num += 1 + } else { + break + } + } + fileHandle.closeFile() + } + return newByte + } + + func WriteData(FileToWrite: String, Offset: Int64, DataToWrite: String) { + if let fileHandle = FileHandle(forWritingAtPath: FileToWrite) { + let newStringArray = DataToWrite.split(separator: "-").map { String($0) } + fileHandle.seek(toFileOffset: UInt64(Offset)) + for str in newStringArray { + if let intValue = Int(str, radix: 16) { + fileHandle.write(Data([UInt8(intValue)])) + } + } + fileHandle.closeFile() + } + } + + func FindOffset(FileName: String, Query: [UInt8]) -> Int64? { + var returnLength: Int64 = -1 + if let fileHandle = FileHandle(forReadingAtPath: FileName) { + let baseStreamLength = fileHandle.seekToEndOfFile() + fileHandle.seek(toFileOffset: 0) + + if Query.count <= baseStreamLength { + var newByteArray = [UInt8](repeating: 0, count: Query.count) + let bytesRead = fileHandle.readData(ofLength: Query.count) + newByteArray = Array(bytesRead) + var flag = false + let newQueryLength = Query.count - 1 + var whileInt = 0 + + while whileInt <= newQueryLength { + if newByteArray[whileInt] == Query[whileInt] { + flag = true + whileInt += 1 + } else { + flag = false + break + } + } + + if !flag { + let newBaseStreamLength = baseStreamLength - 1 + let queryLength = Query.count + var queryOffset = queryLength + + while queryOffset <= newBaseStreamLength { + newByteArray.removeFirst() + newByteArray.append(fileHandle.readData(ofLength: 1).first ?? 0) + var num3 = 0 + while num3 <= newQueryLength { + if newByteArray[num3] == Query[num3] { + flag = true + num3 += 1 + } else { + flag = false + break + } + } + if !flag { + queryOffset += 1 + } else { + returnLength = Int64(queryOffset) - Int64(queryLength - 1) + break + } + } + } else { + returnLength = 0 + } + } + fileHandle.closeFile() + } + return returnLength + } + + func DecryptEBOOT(EBOOTFile: String) { + // Extracts the specified file into the Tools directory + let pspdecrypt = Bundle.main.path(forResource: "pspdecrypt", ofType: "") + let task = Process() + + task.launchPath = "/bin/sh" + task.arguments = ["-c", "'" + pspdecrypt! + "' '" + EBOOTFile + "'"] + task.launch() + task.waitUntilExit() + } + + func CreatefPKG() { + var PKGOutputPath: String = "" + let FolderBrowserOpenPanel = NSOpenPanel() + + FolderBrowserOpenPanel.title = "Select an output folder" + FolderBrowserOpenPanel.showsResizeIndicator = true + FolderBrowserOpenPanel.showsHiddenFiles = false + FolderBrowserOpenPanel.canChooseFiles = false + FolderBrowserOpenPanel.canChooseDirectories = true + FolderBrowserOpenPanel.allowsMultipleSelection = false + + // Set destination folder where the pkg file will be moved into + if (FolderBrowserOpenPanel.runModal() == NSApplication.ModalResponse.OK) { + let result = FolderBrowserOpenPanel.url + PKGOutputPath = result!.path + } else { + return + } + + fPKGBuilderProgressIndicator.stopAnimation(nil) + fPKGBuilderProgressIndicator.isIndeterminate = false + fPKGBuilderProgressIndicator.doubleValue = 0 + fPKGBuilderStatusTextField.stringValue = "Removing old fPKG build folder" + usleep(150000) + + // Remove previous fPKG GP4 project + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/PSPfPKG.gp4") { + do { + try FileManager.default.removeItem(atPath: Utils().CDrivePath + "/Cache/PSPfPKG.gp4") + } catch (let error) { + print(error) + } + } + + // Remove previous fPKG project folder + var isDir: ObjCBool = true + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/PSPfPKG", isDirectory: &isDir) { + do { + try FileManager.default.removeItem(atPath: Utils().CDrivePath + "/Cache/PSPfPKG") + } catch (let error) { + print(error) + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 + fPKGBuilderStatusTextField.stringValue = "Preparing the PSP emulator" + usleep(150000) + + // Copy the PS1 emulator to the new project folder + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/PS4/emus/psphd", isDirectory: &isDir) { + do { + try FileManager.default.copyItem(atPath: Utils().CDrivePath + "/PS4/emus/psphd", toPath: Utils().CDrivePath + "/Cache/PSPfPKG") + } catch (let error) { + print(error) + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 //20 + + // Copy the selected icon to the project folder + if !IconTextField.stringValue.isEmpty { + do { + try FileManager.default.copyItem(atPath: IconTextField.stringValue, toPath: Utils().CDrivePath + "/Cache/PSPfPKG/sce_sys/icon0.png") + } catch (let error) { + print(error) + } + } + + // Copy the selected background to the project folder + if !BackgroundTextField.stringValue.isEmpty { + do { + try FileManager.default.copyItem(atPath: IconTextField.stringValue, toPath: Utils().CDrivePath + "/Cache/PSPfPKG/sce_sys/pic0.png") + } catch (let error) { + print(error) + } + } + + // Get PSP EBOOT + let SelectedISOFile: String = GameISOTextField.stringValue + let ExtractedEBOOTPath: String = ExtractFileFromISO(ISOFile: SelectedISOFile, FileToExtract: "PSP_GAME/SYSDIR/EBOOT.BIN") + let DecryptedEBOOTPath: String = ExtractedEBOOTPath.appending(".dec") + + if !FileManager.default.fileExists(atPath: ExtractedEBOOTPath) { + do { + // Copy ISO file to fPKG cache + try FileManager.default.copyItem(atPath: SelectedISOFile, toPath: Utils().CDrivePath + "/Cache/PSPfPKG/data/USER_L0.IMG") + + fPKGBuilderProgressIndicator.doubleValue += 10 //30 + fPKGBuilderStatusTextField.stringValue = "Waiting for user input" + usleep(150000) + + let IncompatibilityAlert = NSAlert() + IncompatibilityAlert.informativeText = "Warning: This game may not work!" + IncompatibilityAlert.messageText = "Cannot read the EBOOT.BIN file from the ISO." + IncompatibilityAlert.runModal() + } catch (let error) { + print(error) + } + } + else + { + fPKGBuilderProgressIndicator.doubleValue += 10 //30 + fPKGBuilderStatusTextField.stringValue = "Decrypting PSP EBOOT & modifying final image" + usleep(150000) + + // Decrypt extracted EBOOT file + DecryptEBOOT(EBOOTFile: ExtractedEBOOTPath) + + // Copy ISO file to fPKG cache + do { + try FileManager.default.copyItem(atPath: SelectedISOFile, toPath: Utils().CDrivePath + "/Cache/PSPfPKG/data/USER_L0.IMG") + } catch (let error) { + print(error) + } + + let NewFileInfo = try? FileManager.default.attributesOfItem(atPath: ExtractedEBOOTPath) + let FileLength = (NewFileInfo?[.size] as? NSNumber)?.int64Value ?? 0 + let CappedFileLength = min(FileLength, 512320) + + if let TempEBOOTByteArray = ReadUMDData(FileToRead: ExtractedEBOOTPath, Offset: 0, Length: Int(CappedFileLength)), + let OffsetValue = FindOffset(FileName: Utils().CDrivePath + "/Cache/PSPfPKG/data/USER_L0.IMG", Query: TempEBOOTByteArray) { + let DecTempEBOOTByteArray = ReadUMDData(FileToRead: DecryptedEBOOTPath, Offset: 0, Length: Int(FileLength)) + let DataString = DecTempEBOOTByteArray?.map { String(format: "%02X", $0) }.joined(separator: "-") ?? "" + + // Modify ISO + WriteData(FileToWrite: Utils().CDrivePath + "/Cache/PSPfPKG/data/USER_L0.IMG", Offset: OffsetValue, DataToWrite: DataString) + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 //40 + fPKGBuilderStatusTextField.stringValue = "Removing temporary files" + usleep(150000) + + // Remove temporary files + if FileManager.default.fileExists(atPath: ExtractedEBOOTPath) { + do { + try FileManager.default.removeItem(atPath: ExtractedEBOOTPath) + } catch (let error) { + print(error) + } + } + if FileManager.default.fileExists(atPath: DecryptedEBOOTPath) { + do { + try FileManager.default.removeItem(atPath: DecryptedEBOOTPath) + } catch (let error) { + print(error) + } + } + + // If config already exists, remove it + let ConfigDestinationPath: String = Utils().CDrivePath + "/Cache/PSPfPKG/config-title.txt" + if FileManager.default.fileExists(atPath: ConfigDestinationPath) + { + do { + try FileManager.default.removeItem(atPath: ConfigDestinationPath) + } catch (let error) { + print(error) + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 //50 + fPKGBuilderStatusTextField.stringValue = "Configurating PSP Emulator, please wait" + usleep(150000) + + // Create a new config file + FileManager.default.createFile(atPath: ConfigDestinationPath, contents:Data(" ".utf8), attributes: nil) + let ConfigWriter = FileHandle(forWritingAtPath: ConfigDestinationPath) + if ConfigWriter == nil { } + else + { + // Required config + let PSPEmulatorConfig: String = """ + --ps4-trophies=0 + --ps5-uds=0 + --trophies=0 + --image="data/USER_L0.IMG" + --antialias=SSAA4x + --multisaves=true + --notrophies=true + + """ + + ConfigWriter!.write(PSPEmulatorConfig.data(using: .utf8)!) + ConfigWriter!.closeFile() + } + + // Append custom config + if !CustomConfigTextField.stringValue.isEmpty { + let AdditionalConfig = try? String(contentsOfFile: CustomConfigTextField.stringValue, encoding: .utf8) + if let NewFileHandle = FileHandle(forWritingAtPath: ConfigDestinationPath) { + NewFileHandle.seekToEndOfFile() + if let DataToWrite = AdditionalConfig!.data(using: .utf8) { + NewFileHandle.write(DataToWrite) + } + NewFileHandle.closeFile() + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 //60 + fPKGBuilderStatusTextField.stringValue = "PSP emulator configuration done. Creating now the param.sfo file, please wait" + usleep(150000) + + // Create a new param.sfo file + CreateParamSFO(GameTitle: GameTitleTextField.stringValue) + if FileManager.default.fileExists(atPath: Utils().ToolsPath + "/param.sfo") { + + //Delete default param.sfo if exists + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/PSPfPKG/sce_sys/param.sfo") { + do { + try FileManager.default.removeItem(atPath: Utils().CDrivePath + "/Cache/PSPfPKG/sce_sys/param.sfo") + } catch (let error) { + print(error) + } + } + + // Move the new param.sfo to the sce_sys folder + do { + try FileManager.default.moveItem(atPath: Utils().ToolsPath + "/param.sfo", toPath: Utils().CDrivePath + "/Cache/PSPfPKG/sce_sys/param.sfo") + } catch (let error) { + print(error) + } + } + + fPKGBuilderProgressIndicator.doubleValue += 10 //70 + fPKGBuilderStatusTextField.stringValue = "Param.sfo file created. Creating GP4 project" + usleep(150000) + + // Generate a GP4 project and modify it + CreateGP4Project() + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/PSPfPKG.gp4") { + + // Modify the GP4 project + do { + var fileContents = try String(contentsOfFile: Utils().CDrivePath + "/Cache/PSPfPKG.gp4", encoding: .utf8) + fileContents = fileContents.replacingOccurrences(of: "", with: "") + try fileContents.write(toFile: Utils().CDrivePath + "/Cache/PSPfPKG.gp4", atomically: false, encoding: .utf8) + } catch { + print(error) + } + + fPKGBuilderProgressIndicator.doubleValue += 10 //80 + fPKGBuilderStatusTextField.stringValue = "GP4 project created. fPKG will be built after user confirmation" + usleep(100000) + + let PKGReadyAlert = NSAlert() + PKGReadyAlert.messageText = "Files are ready for fPKG creation." + PKGReadyAlert.informativeText = "fPKG Ready" + PKGReadyAlert.addButton(withTitle: "Continue") + PKGReadyAlert.runModal() + + fPKGBuilderProgressIndicator.isIndeterminate = true + fPKGBuilderProgressIndicator.startAnimation(nil) + fPKGBuilderStatusTextField.stringValue = "The fPKG is building, please wait until done" + usleep(150000) + + // Create the fPKG + BuildPKG() + + // Check build + let PKGFileName: String = "UP9000-" + CurrentGameID + "_00-" + CurrentGameID + "PSPFPKG-A0100-V0100.pkg" + if FileManager.default.fileExists(atPath: Utils().CDrivePath + "/Cache/" + PKGFileName) { + + // Move the created fPKG into the selected output folder + do { + try FileManager.default.moveItem(atPath: Utils().CDrivePath + "/Cache/" + PKGFileName, toPath: PKGOutputPath + "/" + PKGFileName) + } catch (let error) { + print(error) + } + + fPKGBuilderProgressIndicator.stopAnimation(nil) + fPKGBuilderProgressIndicator.isIndeterminate = false + fPKGBuilderProgressIndicator.doubleValue = 100 //100 + fPKGBuilderSpinningIndicator.stopAnimation(nil) + fPKGBuilderSpinningIndicator.isHidden = true + fPKGBuilderStatusTextField.stringValue = "Done" + + let SuccessAlert = NSAlert() + SuccessAlert.messageText = "fPKG created with success!" + SuccessAlert.informativeText = "Done" + SuccessAlert.addButton(withTitle: "Continue") + SuccessAlert.runModal() + + } + } + } + + @IBAction func BuildfPKG(_ sender: NSButton) { + + let MissingInfoAlert = NSAlert() + MissingInfoAlert.addButton(withTitle: "Close") + + if GameISOTextField.stringValue.isEmpty { + MissingInfoAlert.messageText = "Game ISO is missing, cannot continue." + MissingInfoAlert.runModal() + return + } + + if GameTitleTextField.stringValue.isEmpty { + MissingInfoAlert.messageText = "The Game Title is missing, cannot continue." + MissingInfoAlert.runModal() + return + } + + if NPTitleTextField.stringValue.isEmpty { + MissingInfoAlert.messageText = "The NP Title is missing, cannot continue." + MissingInfoAlert.runModal() + return + } + + ProgressTextField.isHidden = false + fPKGBuilderProgressIndicator.isHidden = false + fPKGBuilderSpinningIndicator.isHidden = false + fPKGBuilderStatusTextField.isHidden = false + + fPKGBuilderProgressIndicator.isIndeterminate = true + fPKGBuilderProgressIndicator.startAnimation(nil) + fPKGBuilderSpinningIndicator.startAnimation(nil) + fPKGBuilderStatusTextField.stringValue = "Waiting for user input" + + CreatefPKG() + } + + func CreateParamSFO(GameTitle: String) { + let SFOUtil = Bundle.main.path(forResource: "sfoutil", ofType: "") + let ContentID: String = "UP9000-" + CurrentGameID + "_00-" + CurrentGameID + "PSPFPKG" + let DestinationPath: String = "'" + Utils().ToolsPath + "/param.sfo'" + let task = Process() + + task.launchPath = "/bin/sh" + task.arguments = ["-c", "'" + SFOUtil! + "' --force --new-file " + DestinationPath + " --add int APP_TYPE 1 --add str APP_VER \"01.00\" --add int ATTRIBUTE 0 --add str CATEGORY \"gd\" --add str CONTENT_ID \"\(ContentID)\" --add int DOWNLOAD_DATA_SIZE 0 --add str FORMAT \"obs\" --add int PARENTAL_LEVEL 5 --add int SYSTEM_VER 0 --add str TITLE \"" + GameTitle + "\" --add str TITLE_ID \"\(NPTitleTextField.stringValue)\" --add str VERSION \"01.00\""] + + task.launch() + task.waitUntilExit() + } + + func CreateGP4Project() { + let task = Process() + + task.launchPath = "/bin/sh" + task.arguments = ["-c", "'" + Utils().ToolsPath + "/gengp4_psp'"] + task.launch() + task.waitUntilExit() + } + + func BuildPKG() { + let task = Process() + + task.launchPath = "/bin/sh" + task.arguments = ["-c", "'" + Utils().ToolsPath + "/make_fPKG_PSP'"] + task.launch() + task.waitUntilExit() + } + +} diff --git a/macOS/fPKG Builder/Preloader.storyboard b/macOS/fPKG Builder/Preloader.storyboard new file mode 100644 index 0000000..5d9d726 --- /dev/null +++ b/macOS/fPKG Builder/Preloader.storyboard @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macOS/fPKG Builder/PreloaderViewController.swift b/macOS/fPKG Builder/PreloaderViewController.swift new file mode 100644 index 0000000..1d9beed --- /dev/null +++ b/macOS/fPKG Builder/PreloaderViewController.swift @@ -0,0 +1,196 @@ +// +// PreloaderViewController.swift +// fPKG Builder +// +// Created by SvenGDK on 18/08/2024. +// + +import Foundation +import Cocoa + +class PreloaderViewController: NSViewController { + + @IBOutlet weak var LoadingProgressIndicator: NSProgressIndicator! + @IBOutlet weak var LoadingStatusTextField: NSTextField! + + var isDir: ObjCBool = true + + override func viewDidLoad() { + super.viewDidLoad() + + DispatchQueue.main.async { + NSApplication.shared.activate(ignoringOtherApps: true) + self.SetupfPKGBuilder() + } + } + + override var representedObject: Any? { + didSet { + } + } + + func SetupfPKGBuilder() { + let ErrorAlert = NSAlert() + ErrorAlert.addButton(withTitle: "Continue") + + LoadingProgressIndicator.doubleValue = 0 + LoadingStatusTextField.stringValue = "Checking if wine has been installed ..." + sleep(1) + + // Setup wine if app still exists and wine-home/usr folder is missing + if FileManager.default.fileExists(atPath: Utils().ToolsPath + "/Wine Stable.app") && !FileManager.default.fileExists(atPath: Utils().ToolsPath + "/Resources/wine-home/usr", isDirectory: &isDir) { + if CheckWineQuarantine() == true { + + LoadingProgressIndicator.doubleValue += 25 + LoadingStatusTextField.stringValue = "Wine is not installed & quarantined, please wait ..." + sleep(1) + + DequarantineWine() + + if CheckWineQuarantine() == false { + + LoadingProgressIndicator.doubleValue += 25 + LoadingStatusTextField.stringValue = "Wine dequarantined, installing now please wait ..." + sleep(1) + + MoveDequarantinedWine() + + if FileManager.default.fileExists(atPath: Utils().ToolsPath + "/Resources/wine-home/usr", isDirectory: &isDir) { + + // Remove wine app + do { + try FileManager.default.removeItem(atPath: Utils().ToolsPath + "/Wine Stable.app") + } catch (let error) { + print(error) + } + + // Make shell scripts in the Tools folder executable + MakeBinariesExecutable() + + LoadingProgressIndicator.doubleValue += 50 + LoadingStatusTextField.stringValue = "PS Classics fPKG Builder is ready. Starting ..." + sleep(2) + + // Open the main window and close the preloader + let MainStoryBoard = NSStoryboard(name: "Main", bundle: nil) + let PSClassicsTabVC = MainStoryBoard.instantiateController(withIdentifier: "PSClassicsTabViewController") as! PSClassicsTabViewController + let MainWC = MainStoryBoard.instantiateController(withIdentifier: "MainWindowController") as! PSClassicsWindowController + + MainWC.contentViewController = PSClassicsTabVC + MainWC.window?.setContentSize(NSSize(width: 805, height: 650)) + MainWC.showWindow(self) + + self.view.window?.close() + } + else + { + ErrorAlert.messageText = "Could not move wine into the final destination. Errors will happen if you continue." + ErrorAlert.runModal() + } + + } + else + { + ErrorAlert.messageText = "Could not dequarantine wine. Errors will happen if you continue." + ErrorAlert.runModal() + } + + } + else + { + ErrorAlert.messageText = "Could not check quarantine status of wine. Errors will happen if you continue." + ErrorAlert.runModal() + } + } + else + { + // Assuming wine has been installed before + // Open the main window and close the preloader + let MainStoryBoard = NSStoryboard(name: "Main", bundle: nil) + let PSClassicsTabVC = MainStoryBoard.instantiateController(withIdentifier: "PSClassicsTabViewController") as! PSClassicsTabViewController + let MainWC = MainStoryBoard.instantiateController(withIdentifier: "MainWindowController") as! PSClassicsWindowController + + MainWC.contentViewController = PSClassicsTabVC + MainWC.window?.setContentSize(NSSize(width: 805, height: 650)) + MainWC.showWindow(self) + + self.view.window?.close() + } + } + + func CheckWineQuarantine() -> Bool { + var output : [String] = [] + + let task = Process() + task.launchPath = "/bin/sh" + task.arguments = ["-c", "xattr '" + Utils().ToolsPath + "/Wine Stable.app'"] + + let outpipe = Pipe() + task.standardOutput = outpipe + + task.launch() + + let outdata = outpipe.fileHandleForReading.readDataToEndOfFile() + if var string = String(data: outdata, encoding: .utf8) { + string = string.trimmingCharacters(in: .newlines) + output = string.components(separatedBy: "\n") + } + + task.waitUntilExit() + + if !output.isEmpty { + if output.contains("com.apple.quarantine") { + return true + } + else + { + return false + } + } + else + { + return true + } + } + + func DequarantineWine() { + let task = Process() + task.launchPath = "/bin/sh" + task.arguments = ["-c", "xattr -r -d com.apple.quarantine '" + Utils().ToolsPath + "/Wine Stable.app'"] + + let outpipe = Pipe() + task.standardOutput = outpipe + + task.launch() + task.waitUntilExit() + } + + func MoveDequarantinedWine() { + // Create wine-home folder + if !FileManager.default.fileExists(atPath: Utils().ToolsPath + "/Resources/wine-home", isDirectory: &isDir) { + do { + try FileManager.default.createDirectory(atPath: Utils().ToolsPath + "/Resources/wine-home", withIntermediateDirectories: false) + } catch (let error) { + print(error) + } + } + + // Copy wine files to the wine-home/usr folder + if FileManager.default.fileExists(atPath: Utils().ToolsPath + "/Wine Stable.app/Contents/Resources/wine", isDirectory: &isDir) { + do { + try FileManager.default.copyItem(atPath: Utils().ToolsPath + "/Wine Stable.app/Contents/Resources/wine", toPath: Utils().ToolsPath + "/Resources/wine-home/usr") + } catch (let error) { + print(error) + } + } + } + + func MakeBinariesExecutable() { + let task = Process() + task.launchPath = "/bin/sh" + task.arguments = ["-c", "chmod +x '" + Utils().ToolsPath + "/cue2toc' '" + Utils().ToolsPath + "/gengp4_ps1' '" + Utils().ToolsPath + "/gengp4_ps2' '" + Utils().ToolsPath + "/gengp4_psp' '" + Utils().ToolsPath + "/make_fPKG_PS1' '" + Utils().ToolsPath + "/make_fPKG_PS2' '" + Utils().ToolsPath + "/make_fPKG_PSP'"] + task.launch() + task.waitUntilExit() + } + +} diff --git a/macOS/fPKG Builder/Utils.swift b/macOS/fPKG Builder/Utils.swift new file mode 100644 index 0000000..750173f --- /dev/null +++ b/macOS/fPKG Builder/Utils.swift @@ -0,0 +1,23 @@ +// +// Utils.swift +// fPKG Builder +// +// Created by SvenGDK on 17/08/2024. +// + +import Foundation +import UniformTypeIdentifiers + +class Utils { + + // UTTypes for file selection filters + public let BINType: UTType = UTType(tag: "bin", tagClass: .filenameExtension, conformingTo: .archive)! + public let ISOType: UTType = UTType(tag: "iso", tagClass: .filenameExtension, conformingTo: .diskImage)! + public let PNGType: UTType = UTType(tag: "png", tagClass: .filenameExtension, conformingTo: .png)! + public let TXTType: UTType = UTType(tag: "txt", tagClass: .filenameExtension, conformingTo: .text)! + + // Frequently used paths + public let ToolsPath: String = Bundle.main.bundleURL.deletingLastPathComponent().path + "/Tools" + public let CDrivePath: String = Bundle.main.bundleURL.deletingLastPathComponent().path + "/Tools/Resources/wine-prefix/drive_c" + +} diff --git a/macOS/fPKG Builder/fPKG_Builder.entitlements b/macOS/fPKG Builder/fPKG_Builder.entitlements new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/macOS/fPKG Builder/fPKG_Builder.entitlements @@ -0,0 +1,5 @@ + + + + + diff --git a/macOS/fPKG Builder/isoinfo b/macOS/fPKG Builder/isoinfo new file mode 100755 index 0000000..9f5f0ba Binary files /dev/null and b/macOS/fPKG Builder/isoinfo differ diff --git a/macOS/fPKG Builder/pspdecrypt b/macOS/fPKG Builder/pspdecrypt new file mode 100755 index 0000000..0ba996e Binary files /dev/null and b/macOS/fPKG Builder/pspdecrypt differ diff --git a/macOS/fPKG Builder/sfoutil b/macOS/fPKG Builder/sfoutil new file mode 100755 index 0000000..a961e53 Binary files /dev/null and b/macOS/fPKG Builder/sfoutil differ diff --git a/macOS/fPKG Builder/unar b/macOS/fPKG Builder/unar new file mode 100755 index 0000000..4887309 Binary files /dev/null and b/macOS/fPKG Builder/unar differ