First commit

First commit contains v1 of PS Classics fPKG Builder for macOS.
This commit is contained in:
SvenGDK
2024-08-18 19:19:39 +02:00
parent 9c3df4b66f
commit 74364ee94c
28 changed files with 4659 additions and 0 deletions
+2
View File
@@ -60,3 +60,5 @@ fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
**/.DS_Store
@@ -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 = "<group>"; };
C2DC09482C6D2F46009897FF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
C2DC094B2C6D2F46009897FF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
C2DC094D2C6D2F46009897FF /* fPKG_Builder.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = fPKG_Builder.entitlements; sourceTree = "<group>"; };
C2DC09532C6D3068009897FF /* PSClassicsWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PSClassicsWindowController.swift; sourceTree = "<group>"; };
C2DC09542C6D3068009897FF /* PS2ClassicsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PS2ClassicsViewController.swift; sourceTree = "<group>"; };
C2DC09552C6D3068009897FF /* PSPClassicsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PSPClassicsViewController.swift; sourceTree = "<group>"; };
C2DC09562C6D3068009897FF /* PS1ClassicsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PS1ClassicsViewController.swift; sourceTree = "<group>"; };
C2E921532C70FAE600DEC0FC /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
C2E921552C7110C800DEC0FC /* isoinfo */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = isoinfo; sourceTree = "<group>"; };
C2E921572C71219700DEC0FC /* unar */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = unar; sourceTree = "<group>"; };
C2E921592C71E96C00DEC0FC /* pspdecrypt */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = pspdecrypt; sourceTree = "<group>"; };
C2E9215D2C71F6F000DEC0FC /* sfoutil */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = sfoutil; sourceTree = "<group>"; };
C2E9215F2C72298400DEC0FC /* Preloader.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Preloader.storyboard; sourceTree = "<group>"; };
C2E921612C722C1200DEC0FC /* PreloaderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreloaderViewController.swift; sourceTree = "<group>"; };
C2E921632C72409200DEC0FC /* PSClassicsTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PSClassicsTabViewController.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
};
C2DC09422C6D2F45009897FF /* Products */ = {
isa = PBXGroup;
children = (
C2DC09412C6D2F45009897FF /* fPKG Builder.app */,
);
name = Products;
sourceTree = "<group>";
};
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 = "<group>";
};
/* 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 = "<group>";
};
/* 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 */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
+27
View File
@@ -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
}
}
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -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
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@@ -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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because it is too large Load Diff
@@ -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 <file targ_path=\"data/disc1.cue\" orig_path=\"\(Disc1CUEFilePath)\" pfs_compression=\"enable\"/>"
let XMLDisc1BinPath = "\n <file targ_path=\"data/disc1.bin\" orig_path=\"\(Disc1BINFilePath)\" pfs_compression=\"enable\"/>"
do {
var fileContents = try String(contentsOfFile: Utils().CDrivePath + "/Cache/PS1fPKG.gp4", encoding: .utf8)
fileContents = fileContents.replacingOccurrences(of: "<?xml version=\"1.1\"", with: "<?xml version=\"1.0\"")
fileContents = fileContents.replacingOccurrences(of: "<scenarios default_id=\"1\">", with: "<scenarios default_id=\"0\">")
fileContents = fileContents.replacingOccurrences(of: "</files>", with: "\(XMLDisc1CuePath)\(XMLDisc1BinPath)\n</files>")
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 <file targ_path=\"data/disc2.cue\" orig_path=\"\(Disc2CUEFilePath)\" pfs_compression=\"enable\"/>"
let XMLDisc2BinPath = "\n <file targ_path=\"data/disc2.bin\" orig_path=\"\(Disc2BINFilePath)\" pfs_compression=\"enable\"/>"
do {
var fileContents = try String(contentsOfFile: Utils().CDrivePath + "/Cache/PS1fPKG.gp4", encoding: .utf8)
fileContents = fileContents.replacingOccurrences(of: "</files>", with: "\(XMLDisc2CuePath)\(XMLDisc2BinPath)\n</files>")
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 <file targ_path=\"data/disc3.cue\" orig_path=\"\(Disc3CUEFilePath)\" pfs_compression=\"enable\"/>"
let XMLDisc3BinPath = "\n <file targ_path=\"data/disc3.bin\" orig_path=\"\(Disc3BINFilePath)\" pfs_compression=\"enable\"/>"
do {
var fileContents = try String(contentsOfFile: Utils().CDrivePath + "/Cache/PS1fPKG.gp4", encoding: .utf8)
fileContents = fileContents.replacingOccurrences(of: "</files>", with: "\(XMLDisc3CuePath)\(XMLDisc3BinPath)\n</files>")
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 <file targ_path=\"data/disc4.cue\" orig_path=\"\(Disc4CUEFilePath)\" pfs_compression=\"enable\"/>"
let XMLDisc4BinPath = "\n <file targ_path=\"data/disc4.bin\" orig_path=\"\(Disc4BINFilePath)\" pfs_compression=\"enable\"/>"
do {
var fileContents = try String(contentsOfFile: Utils().CDrivePath + "/Cache/PS1fPKG.gp4", encoding: .utf8)
fileContents = fileContents.replacingOccurrences(of: "</files>", with: "\(XMLDisc4CuePath)\(XMLDisc4BinPath)\n</files>")
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()
}
}
@@ -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 <file targ_path=\"image/disc01.iso\" orig_path=\"\(Disc1ISOFilePath)\" pfs_compression=\"enable\"/>"]
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 <file targ_path=\"image/disc02.iso\" orig_path=\"\(Disc2ISOFilePath)\" pfs_compression=\"enable\"/>"]
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 <file targ_path=\"image/disc03.iso\" orig_path=\"\(Disc3ISOFilePath)\" pfs_compression=\"enable\"/>"]
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 <file targ_path=\"image/disc04.iso\" orig_path=\"\(Disc4ISOFilePath)\" pfs_compression=\"enable\"/>"]
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 <file targ_path=\"image/disc05.iso\" orig_path=\"\(Disc5ISOFilePath)\" pfs_compression=\"enable\"/>"]
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: "<?xml version=\"1.1\"", with: "<?xml version=\"1.0\"")
fileContents = fileContents.replacingOccurrences(of: "<scenarios default_id=\"1\">", with: "<scenarios default_id=\"0\">")
fileContents = fileContents.replacingOccurrences(of: "</files>", with: "\(FullDiscInfo)\n</files>")
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()
}
}
@@ -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 {
}
}
}
@@ -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()
}
}
@@ -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: "<?xml version=\"1.1\"", with: "<?xml version=\"1.0\"")
fileContents = fileContents.replacingOccurrences(of: "<scenarios default_id=\"1\">", with: "<scenarios default_id=\"0\">")
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()
}
}
+76
View File
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="RgM-zw-Kyg">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22690"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Window Controller-->
<scene sceneID="RhC-Y2-v22">
<objects>
<windowController id="RgM-zw-Kyg" sceneMemberID="viewController">
<window key="window" title="PS Classics fPKG Builder for macOS [by SvenGDK]" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="bKe-r2-B5n">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="425" y="462" width="480" height="270"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<view key="contentView" id="gBb-NI-ZjB">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>
</view>
<connections>
<outlet property="delegate" destination="RgM-zw-Kyg" id="V4I-jq-e5I"/>
</connections>
</window>
<connections>
<segue destination="E2r-yS-ssk" kind="relationship" relationship="window.shadowedContentViewController" id="uVo-8X-puB"/>
</connections>
</windowController>
<customObject id="siQ-VN-W1N" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-18" y="18"/>
</scene>
<!--Preloader View Controller-->
<scene sceneID="EBV-j7-AZH">
<objects>
<viewController id="E2r-yS-ssk" customClass="PreloaderViewController" customModule="fPKG_Builder" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="dgd-yP-Q5T">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<progressIndicator fixedFrame="YES" maxValue="100" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="fS8-oT-1Mi">
<rect key="frame" x="48" y="121" width="384" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</progressIndicator>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Xco-oh-R2J">
<rect key="frame" x="46" y="148" width="388" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Please wait until the fPKG Builder finished initializing" id="slo-vB-YAj">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DyG-lw-2YN">
<rect key="frame" x="46" y="106" width="388" height="14"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" alignment="center" title="Checking ..." id="gX3-mg-cFx">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</view>
<connections>
<outlet property="LoadingProgressIndicator" destination="fS8-oT-1Mi" id="ezC-ki-YvN"/>
<outlet property="LoadingStatusTextField" destination="DyG-lw-2YN" id="05b-c0-H9l"/>
</connections>
</viewController>
<customObject id="ea9-8Z-f5x" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="586" y="10"/>
</scene>
</scenes>
</document>
@@ -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()
}
}
+23
View File
@@ -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"
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.