├── .git-blame-ignore-revs ├── .gitignore ├── LICENSE ├── M1Craft-Info.plist ├── M1Craft.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ └── M1Craft UI.xcscheme ├── M1Craft ├── AppState.swift ├── Array+RawRepresentable.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ └── Contents.json ├── Constants.swift ├── Localization │ └── Localizable.strings ├── M1Craft App.swift ├── M1Craft-Debug.entitlements ├── M1Craft.entitlements └── Views │ ├── AuthView.swift │ ├── ContentView.swift │ ├── MainWindow.swift │ ├── Preflight.swift │ ├── RefreshAuthView.swift │ ├── SettingsView.swift │ ├── SparkleView.swift │ ├── VersionList │ ├── VersionListRowView.swift │ └── VersionListView.swift │ └── Xcodes │ └── AppStoreButtonStyle.swift ├── README.md └── readme-fr.md /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | 21672816604f7f8e0c179ca8f97ffd6eb271cf33 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ezekiel Elin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /M1Craft-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleURLTypes 6 | 7 | 8 | CFBundleTypeRole 9 | Viewer 10 | CFBundleURLName 11 | dev.ezekiel.m1craft.auth-url 12 | CFBundleURLSchemes 13 | 14 | m1craft 15 | 16 | 17 | 18 | SUFeedURL 19 | https://f001.backblazeb2.com/file/minecraft-jar-command/appcast/appcast 20 | SUPublicEDKey 21 | tTfJT2EdZII20PCJ41k439wI+pehbxcOPdY2fToSQKo= 22 | 23 | 24 | -------------------------------------------------------------------------------- /M1Craft.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6604C265271E1EF80024898E /* M1Craft App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6604C264271E1EF80024898E /* M1Craft App.swift */; }; 11 | 6604C269271E1EF90024898E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6604C268271E1EF90024898E /* Assets.xcassets */; }; 12 | 6604C278271E1FD80024898E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6604C277271E1FD80024898E /* ContentView.swift */; }; 13 | 6604C27B271E1FFC0024898E /* InstallationManager in Frameworks */ = {isa = PBXBuildFile; productRef = 6604C27A271E1FFC0024898E /* InstallationManager */; }; 14 | 6610CB9B28577A5E00677E6E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6610CB9A28577A5E00677E6E /* Localizable.strings */; }; 15 | 6640C3CD27FE442500831E89 /* Array+RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6640C3CC27FE442400831E89 /* Array+RawRepresentable.swift */; }; 16 | 66530F3427C6086900CD94DE /* MainWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66530F3327C6086900CD94DE /* MainWindow.swift */; }; 17 | 66530F3727C60C1A00CD94DE /* VersionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66530F3627C60C1A00CD94DE /* VersionListView.swift */; }; 18 | 66530F3927C60C8800CD94DE /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66530F3827C60C8800CD94DE /* AppState.swift */; }; 19 | 66530F3B27C6BD2000CD94DE /* VersionListRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66530F3A27C6BD2000CD94DE /* VersionListRowView.swift */; }; 20 | 66530F3E27C6BFD500CD94DE /* AppStoreButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66530F3D27C6BFD500CD94DE /* AppStoreButtonStyle.swift */; }; 21 | 66648ECC2742DCE5005CD396 /* RefreshAuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66648ECB2742DCE5005CD396 /* RefreshAuthView.swift */; }; 22 | 66648ECE27431764005CD396 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66648ECD27431764005CD396 /* Constants.swift */; }; 23 | 66A0719C279A6180002311E4 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66A0719B279A6180002311E4 /* SettingsView.swift */; }; 24 | 66B9CBFB278B3265001A59E2 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 66B9CBFA278B3265001A59E2 /* Sparkle */; }; 25 | 66B9CBFD278B327A001A59E2 /* SparkleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66B9CBFC278B327A001A59E2 /* SparkleView.swift */; }; 26 | 66D996812741C8E3000D53DD /* Preflight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66D996802741C8E3000D53DD /* Preflight.swift */; }; 27 | 66F13B1B273DCEA9002C297B /* AuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F13B1A273DCEA9002C297B /* AuthView.swift */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 6604C261271E1EF80024898E /* M1Craft.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = M1Craft.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 6604C264271E1EF80024898E /* M1Craft App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "M1Craft App.swift"; sourceTree = ""; }; 33 | 6604C268271E1EF90024898E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 34 | 6604C26D271E1EF90024898E /* M1Craft.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = M1Craft.entitlements; sourceTree = ""; }; 35 | 6604C277271E1FD80024898E /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 36 | 6610CB9A28577A5E00677E6E /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; 37 | 6640C3CC27FE442400831E89 /* Array+RawRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+RawRepresentable.swift"; sourceTree = ""; }; 38 | 66530F3327C6086900CD94DE /* MainWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWindow.swift; sourceTree = ""; }; 39 | 66530F3627C60C1A00CD94DE /* VersionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionListView.swift; sourceTree = ""; }; 40 | 66530F3827C60C8800CD94DE /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; 41 | 66530F3A27C6BD2000CD94DE /* VersionListRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionListRowView.swift; sourceTree = ""; }; 42 | 66530F3D27C6BFD500CD94DE /* AppStoreButtonStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreButtonStyle.swift; sourceTree = ""; }; 43 | 66648ECB2742DCE5005CD396 /* RefreshAuthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshAuthView.swift; sourceTree = ""; }; 44 | 66648ECD27431764005CD396 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 45 | 66A0719B279A6180002311E4 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 46 | 66B8AA03278D299D00E41FA1 /* M1Craft-Debug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "M1Craft-Debug.entitlements"; sourceTree = ""; }; 47 | 66B9CBFC278B327A001A59E2 /* SparkleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SparkleView.swift; sourceTree = ""; }; 48 | 66D996802741C8E3000D53DD /* Preflight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preflight.swift; sourceTree = ""; }; 49 | 66F13B19273DCE35002C297B /* M1Craft-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "M1Craft-Info.plist"; sourceTree = SOURCE_ROOT; }; 50 | 66F13B1A273DCEA9002C297B /* AuthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthView.swift; sourceTree = ""; }; 51 | /* End PBXFileReference section */ 52 | 53 | /* Begin PBXFrameworksBuildPhase section */ 54 | 6604C25E271E1EF80024898E /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | 6604C27B271E1FFC0024898E /* InstallationManager in Frameworks */, 59 | 66B9CBFB278B3265001A59E2 /* Sparkle in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | 6603695D278E3D4C000D9EFF /* Views */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 66530F3C27C6BFA700CD94DE /* Xcodes */, 70 | 66530F3527C60C0000CD94DE /* VersionList */, 71 | 66D996802741C8E3000D53DD /* Preflight.swift */, 72 | 66F13B1A273DCEA9002C297B /* AuthView.swift */, 73 | 66648ECB2742DCE5005CD396 /* RefreshAuthView.swift */, 74 | 6604C277271E1FD80024898E /* ContentView.swift */, 75 | 66B9CBFC278B327A001A59E2 /* SparkleView.swift */, 76 | 66A0719B279A6180002311E4 /* SettingsView.swift */, 77 | 66530F3327C6086900CD94DE /* MainWindow.swift */, 78 | ); 79 | path = Views; 80 | sourceTree = ""; 81 | }; 82 | 6604C258271E1EF80024898E = { 83 | isa = PBXGroup; 84 | children = ( 85 | 6604C263271E1EF80024898E /* M1Craft */, 86 | 6604C262271E1EF80024898E /* Products */, 87 | ); 88 | sourceTree = ""; 89 | }; 90 | 6604C262271E1EF80024898E /* Products */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 6604C261271E1EF80024898E /* M1Craft.app */, 94 | ); 95 | name = Products; 96 | sourceTree = ""; 97 | }; 98 | 6604C263271E1EF80024898E /* M1Craft */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 6610CB9928577A5400677E6E /* Localization */, 102 | 66B8AA03278D299D00E41FA1 /* M1Craft-Debug.entitlements */, 103 | 66F13B19273DCE35002C297B /* M1Craft-Info.plist */, 104 | 6604C264271E1EF80024898E /* M1Craft App.swift */, 105 | 66530F3827C60C8800CD94DE /* AppState.swift */, 106 | 6603695D278E3D4C000D9EFF /* Views */, 107 | 66648ECD27431764005CD396 /* Constants.swift */, 108 | 6640C3CC27FE442400831E89 /* Array+RawRepresentable.swift */, 109 | 6604C268271E1EF90024898E /* Assets.xcassets */, 110 | 6604C26D271E1EF90024898E /* M1Craft.entitlements */, 111 | ); 112 | path = M1Craft; 113 | sourceTree = ""; 114 | }; 115 | 6610CB9928577A5400677E6E /* Localization */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 6610CB9A28577A5E00677E6E /* Localizable.strings */, 119 | ); 120 | path = Localization; 121 | sourceTree = ""; 122 | }; 123 | 66530F3527C60C0000CD94DE /* VersionList */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 66530F3627C60C1A00CD94DE /* VersionListView.swift */, 127 | 66530F3A27C6BD2000CD94DE /* VersionListRowView.swift */, 128 | ); 129 | path = VersionList; 130 | sourceTree = ""; 131 | }; 132 | 66530F3C27C6BFA700CD94DE /* Xcodes */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 66530F3D27C6BFD500CD94DE /* AppStoreButtonStyle.swift */, 136 | ); 137 | path = Xcodes; 138 | sourceTree = ""; 139 | }; 140 | /* End PBXGroup section */ 141 | 142 | /* Begin PBXNativeTarget section */ 143 | 6604C260271E1EF80024898E /* M1Craft */ = { 144 | isa = PBXNativeTarget; 145 | buildConfigurationList = 6604C270271E1EF90024898E /* Build configuration list for PBXNativeTarget "M1Craft" */; 146 | buildPhases = ( 147 | 6604C25D271E1EF80024898E /* Sources */, 148 | 6604C25E271E1EF80024898E /* Frameworks */, 149 | 6604C25F271E1EF80024898E /* Resources */, 150 | ); 151 | buildRules = ( 152 | ); 153 | dependencies = ( 154 | ); 155 | name = M1Craft; 156 | packageProductDependencies = ( 157 | 6604C27A271E1FFC0024898E /* InstallationManager */, 158 | 66B9CBFA278B3265001A59E2 /* Sparkle */, 159 | ); 160 | productName = "M1Craft UI"; 161 | productReference = 6604C261271E1EF80024898E /* M1Craft.app */; 162 | productType = "com.apple.product-type.application"; 163 | }; 164 | /* End PBXNativeTarget section */ 165 | 166 | /* Begin PBXProject section */ 167 | 6604C259271E1EF80024898E /* Project object */ = { 168 | isa = PBXProject; 169 | attributes = { 170 | BuildIndependentTargetsInParallel = 1; 171 | LastSwiftUpdateCheck = 1310; 172 | LastUpgradeCheck = 1410; 173 | TargetAttributes = { 174 | 6604C260271E1EF80024898E = { 175 | CreatedOnToolsVersion = 13.1; 176 | }; 177 | }; 178 | }; 179 | buildConfigurationList = 6604C25C271E1EF80024898E /* Build configuration list for PBXProject "M1Craft" */; 180 | compatibilityVersion = "Xcode 14.0"; 181 | developmentRegion = en; 182 | hasScannedForEncodings = 0; 183 | knownRegions = ( 184 | en, 185 | Base, 186 | fr, 187 | ); 188 | mainGroup = 6604C258271E1EF80024898E; 189 | packageReferences = ( 190 | 6604C279271E1FFC0024898E /* XCRemoteSwiftPackageReference "minecraft-jar-command" */, 191 | 66B9CBF9278B3265001A59E2 /* XCRemoteSwiftPackageReference "Sparkle" */, 192 | ); 193 | productRefGroup = 6604C262271E1EF80024898E /* Products */; 194 | projectDirPath = ""; 195 | projectRoot = ""; 196 | targets = ( 197 | 6604C260271E1EF80024898E /* M1Craft */, 198 | ); 199 | }; 200 | /* End PBXProject section */ 201 | 202 | /* Begin PBXResourcesBuildPhase section */ 203 | 6604C25F271E1EF80024898E /* Resources */ = { 204 | isa = PBXResourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 6604C269271E1EF90024898E /* Assets.xcassets in Resources */, 208 | 6610CB9B28577A5E00677E6E /* Localizable.strings in Resources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXResourcesBuildPhase section */ 213 | 214 | /* Begin PBXSourcesBuildPhase section */ 215 | 6604C25D271E1EF80024898E /* Sources */ = { 216 | isa = PBXSourcesBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | 66648ECC2742DCE5005CD396 /* RefreshAuthView.swift in Sources */, 220 | 6604C265271E1EF80024898E /* M1Craft App.swift in Sources */, 221 | 66530F3B27C6BD2000CD94DE /* VersionListRowView.swift in Sources */, 222 | 6604C278271E1FD80024898E /* ContentView.swift in Sources */, 223 | 66530F3927C60C8800CD94DE /* AppState.swift in Sources */, 224 | 66F13B1B273DCEA9002C297B /* AuthView.swift in Sources */, 225 | 66A0719C279A6180002311E4 /* SettingsView.swift in Sources */, 226 | 6640C3CD27FE442500831E89 /* Array+RawRepresentable.swift in Sources */, 227 | 66D996812741C8E3000D53DD /* Preflight.swift in Sources */, 228 | 66530F3E27C6BFD500CD94DE /* AppStoreButtonStyle.swift in Sources */, 229 | 66648ECE27431764005CD396 /* Constants.swift in Sources */, 230 | 66B9CBFD278B327A001A59E2 /* SparkleView.swift in Sources */, 231 | 66530F3427C6086900CD94DE /* MainWindow.swift in Sources */, 232 | 66530F3727C60C1A00CD94DE /* VersionListView.swift in Sources */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | /* End PBXSourcesBuildPhase section */ 237 | 238 | /* Begin XCBuildConfiguration section */ 239 | 6604C26E271E1EF90024898E /* Debug */ = { 240 | isa = XCBuildConfiguration; 241 | buildSettings = { 242 | ALWAYS_SEARCH_USER_PATHS = NO; 243 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 244 | CLANG_ANALYZER_NONNULL = YES; 245 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 246 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 247 | CLANG_CXX_LIBRARY = "libc++"; 248 | CLANG_ENABLE_MODULES = YES; 249 | CLANG_ENABLE_OBJC_ARC = YES; 250 | CLANG_ENABLE_OBJC_WEAK = YES; 251 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 252 | CLANG_WARN_BOOL_CONVERSION = YES; 253 | CLANG_WARN_COMMA = YES; 254 | CLANG_WARN_CONSTANT_CONVERSION = YES; 255 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 256 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 257 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 258 | CLANG_WARN_EMPTY_BODY = YES; 259 | CLANG_WARN_ENUM_CONVERSION = YES; 260 | CLANG_WARN_INFINITE_RECURSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 264 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 265 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 266 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 267 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 268 | CLANG_WARN_STRICT_PROTOTYPES = YES; 269 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 270 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 271 | CLANG_WARN_UNREACHABLE_CODE = YES; 272 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 273 | COPY_PHASE_STRIP = NO; 274 | DEAD_CODE_STRIPPING = YES; 275 | DEBUG_INFORMATION_FORMAT = dwarf; 276 | ENABLE_STRICT_OBJC_MSGSEND = YES; 277 | ENABLE_TESTABILITY = YES; 278 | GCC_C_LANGUAGE_STANDARD = gnu11; 279 | GCC_DYNAMIC_NO_PIC = NO; 280 | GCC_NO_COMMON_BLOCKS = YES; 281 | GCC_OPTIMIZATION_LEVEL = 0; 282 | GCC_PREPROCESSOR_DEFINITIONS = ( 283 | "DEBUG=1", 284 | "$(inherited)", 285 | ); 286 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 287 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 288 | GCC_WARN_UNDECLARED_SELECTOR = YES; 289 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 290 | GCC_WARN_UNUSED_FUNCTION = YES; 291 | GCC_WARN_UNUSED_VARIABLE = YES; 292 | MACOSX_DEPLOYMENT_TARGET = 12.4; 293 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 294 | MTL_FAST_MATH = YES; 295 | ONLY_ACTIVE_ARCH = YES; 296 | SDKROOT = macosx; 297 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 298 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 299 | }; 300 | name = Debug; 301 | }; 302 | 6604C26F271E1EF90024898E /* Release */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ALWAYS_SEARCH_USER_PATHS = NO; 306 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 307 | CLANG_ANALYZER_NONNULL = YES; 308 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 309 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 310 | CLANG_CXX_LIBRARY = "libc++"; 311 | CLANG_ENABLE_MODULES = YES; 312 | CLANG_ENABLE_OBJC_ARC = YES; 313 | CLANG_ENABLE_OBJC_WEAK = YES; 314 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 315 | CLANG_WARN_BOOL_CONVERSION = YES; 316 | CLANG_WARN_COMMA = YES; 317 | CLANG_WARN_CONSTANT_CONVERSION = YES; 318 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 319 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 320 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 321 | CLANG_WARN_EMPTY_BODY = YES; 322 | CLANG_WARN_ENUM_CONVERSION = YES; 323 | CLANG_WARN_INFINITE_RECURSION = YES; 324 | CLANG_WARN_INT_CONVERSION = YES; 325 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 326 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 327 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 328 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 329 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 330 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 331 | CLANG_WARN_STRICT_PROTOTYPES = YES; 332 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 333 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 334 | CLANG_WARN_UNREACHABLE_CODE = YES; 335 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 336 | COPY_PHASE_STRIP = NO; 337 | DEAD_CODE_STRIPPING = YES; 338 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 339 | ENABLE_NS_ASSERTIONS = NO; 340 | ENABLE_STRICT_OBJC_MSGSEND = YES; 341 | GCC_C_LANGUAGE_STANDARD = gnu11; 342 | GCC_NO_COMMON_BLOCKS = YES; 343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 345 | GCC_WARN_UNDECLARED_SELECTOR = YES; 346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 347 | GCC_WARN_UNUSED_FUNCTION = YES; 348 | GCC_WARN_UNUSED_VARIABLE = YES; 349 | MACOSX_DEPLOYMENT_TARGET = 12.4; 350 | MTL_ENABLE_DEBUG_INFO = NO; 351 | MTL_FAST_MATH = YES; 352 | SDKROOT = macosx; 353 | SWIFT_COMPILATION_MODE = wholemodule; 354 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 355 | }; 356 | name = Release; 357 | }; 358 | 6604C271271E1EF90024898E /* Debug */ = { 359 | isa = XCBuildConfiguration; 360 | buildSettings = { 361 | ARCHS = "$(ARCHS_STANDARD)"; 362 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 363 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 364 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; 365 | CODE_SIGN_ENTITLEMENTS = "M1Craft/M1Craft-Debug.entitlements"; 366 | CODE_SIGN_IDENTITY = "-"; 367 | CODE_SIGN_STYLE = Automatic; 368 | COMBINE_HIDPI_IMAGES = YES; 369 | CURRENT_PROJECT_VERSION = 21; 370 | DEAD_CODE_STRIPPING = YES; 371 | DEVELOPMENT_ASSET_PATHS = ""; 372 | DEVELOPMENT_TEAM = 39QG79F7FD; 373 | ENABLE_HARDENED_RUNTIME = YES; 374 | ENABLE_PREVIEWS = YES; 375 | GENERATE_INFOPLIST_FILE = YES; 376 | INFOPLIST_FILE = "M1Craft-Info.plist"; 377 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games"; 378 | LD_RUNPATH_SEARCH_PATHS = ( 379 | "$(inherited)", 380 | "@executable_path/../Frameworks", 381 | ); 382 | MACOSX_DEPLOYMENT_TARGET = 12.4; 383 | MARKETING_VERSION = 2.1.2; 384 | PRODUCT_BUNDLE_IDENTIFIER = dev.ezekiel.m1craft; 385 | PRODUCT_NAME = "$(TARGET_NAME)"; 386 | SWIFT_EMIT_LOC_STRINGS = YES; 387 | SWIFT_VERSION = 5.0; 388 | }; 389 | name = Debug; 390 | }; 391 | 6604C272271E1EF90024898E /* Release */ = { 392 | isa = XCBuildConfiguration; 393 | buildSettings = { 394 | ARCHS = "$(ARCHS_STANDARD)"; 395 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 396 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 397 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; 398 | CODE_SIGN_ENTITLEMENTS = M1Craft/M1Craft.entitlements; 399 | CODE_SIGN_IDENTITY = "-"; 400 | CODE_SIGN_STYLE = Automatic; 401 | COMBINE_HIDPI_IMAGES = YES; 402 | CURRENT_PROJECT_VERSION = 21; 403 | DEAD_CODE_STRIPPING = YES; 404 | DEVELOPMENT_ASSET_PATHS = ""; 405 | DEVELOPMENT_TEAM = 39QG79F7FD; 406 | ENABLE_HARDENED_RUNTIME = YES; 407 | ENABLE_PREVIEWS = YES; 408 | GENERATE_INFOPLIST_FILE = YES; 409 | INFOPLIST_FILE = "M1Craft-Info.plist"; 410 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games"; 411 | LD_RUNPATH_SEARCH_PATHS = ( 412 | "$(inherited)", 413 | "@executable_path/../Frameworks", 414 | ); 415 | MACOSX_DEPLOYMENT_TARGET = 12.4; 416 | MARKETING_VERSION = 2.1.2; 417 | PRODUCT_BUNDLE_IDENTIFIER = dev.ezekiel.m1craft; 418 | PRODUCT_NAME = "$(TARGET_NAME)"; 419 | SWIFT_EMIT_LOC_STRINGS = YES; 420 | SWIFT_VERSION = 5.0; 421 | }; 422 | name = Release; 423 | }; 424 | /* End XCBuildConfiguration section */ 425 | 426 | /* Begin XCConfigurationList section */ 427 | 6604C25C271E1EF80024898E /* Build configuration list for PBXProject "M1Craft" */ = { 428 | isa = XCConfigurationList; 429 | buildConfigurations = ( 430 | 6604C26E271E1EF90024898E /* Debug */, 431 | 6604C26F271E1EF90024898E /* Release */, 432 | ); 433 | defaultConfigurationIsVisible = 0; 434 | defaultConfigurationName = Release; 435 | }; 436 | 6604C270271E1EF90024898E /* Build configuration list for PBXNativeTarget "M1Craft" */ = { 437 | isa = XCConfigurationList; 438 | buildConfigurations = ( 439 | 6604C271271E1EF90024898E /* Debug */, 440 | 6604C272271E1EF90024898E /* Release */, 441 | ); 442 | defaultConfigurationIsVisible = 0; 443 | defaultConfigurationName = Release; 444 | }; 445 | /* End XCConfigurationList section */ 446 | 447 | /* Begin XCRemoteSwiftPackageReference section */ 448 | 6604C279271E1FFC0024898E /* XCRemoteSwiftPackageReference "minecraft-jar-command" */ = { 449 | isa = XCRemoteSwiftPackageReference; 450 | repositoryURL = "https://github.com/ezfe/minecraft-jar-command"; 451 | requirement = { 452 | branch = main; 453 | kind = branch; 454 | }; 455 | }; 456 | 66B9CBF9278B3265001A59E2 /* XCRemoteSwiftPackageReference "Sparkle" */ = { 457 | isa = XCRemoteSwiftPackageReference; 458 | repositoryURL = "https://github.com/sparkle-project/Sparkle"; 459 | requirement = { 460 | kind = upToNextMajorVersion; 461 | minimumVersion = 2.0.0; 462 | }; 463 | }; 464 | /* End XCRemoteSwiftPackageReference section */ 465 | 466 | /* Begin XCSwiftPackageProductDependency section */ 467 | 6604C27A271E1FFC0024898E /* InstallationManager */ = { 468 | isa = XCSwiftPackageProductDependency; 469 | package = 6604C279271E1FFC0024898E /* XCRemoteSwiftPackageReference "minecraft-jar-command" */; 470 | productName = InstallationManager; 471 | }; 472 | 66B9CBFA278B3265001A59E2 /* Sparkle */ = { 473 | isa = XCSwiftPackageProductDependency; 474 | package = 66B9CBF9278B3265001A59E2 /* XCRemoteSwiftPackageReference "Sparkle" */; 475 | productName = Sparkle; 476 | }; 477 | /* End XCSwiftPackageProductDependency section */ 478 | }; 479 | rootObject = 6604C259271E1EF80024898E /* Project object */; 480 | } 481 | -------------------------------------------------------------------------------- /M1Craft.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /M1Craft.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /M1Craft.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "minecraft-jar-command", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/ezfe/minecraft-jar-command", 7 | "state" : { 8 | "branch" : "main", 9 | "revision" : "1513411308459a6d57249e5e8b35f0ec8872a974" 10 | } 11 | }, 12 | { 13 | "identity" : "sparkle", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/sparkle-project/Sparkle", 16 | "state" : { 17 | "revision" : "8e27997de02457a0c4690ffcfb8ac9c8e1066883", 18 | "version" : "2.2.2" 19 | } 20 | }, 21 | { 22 | "identity" : "swift-crypto", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/apple/swift-crypto.git", 25 | "state" : { 26 | "revision" : "ddb07e896a2a8af79512543b1c7eb9797f8898a5", 27 | "version" : "1.1.7" 28 | } 29 | }, 30 | { 31 | "identity" : "zip", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/marmelroy/Zip.git", 34 | "state" : { 35 | "revision" : "67fa55813b9e7b3b9acee9c0ae501def28746d76", 36 | "version" : "2.1.2" 37 | } 38 | } 39 | ], 40 | "version" : 2 41 | } 42 | -------------------------------------------------------------------------------- /M1Craft.xcodeproj/xcshareddata/xcschemes/M1Craft UI.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /M1Craft/AppState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppState.swift 3 | // M1Craft 4 | // 5 | // Created by Ezekiel Elin on 2/23/22. 6 | // 7 | 8 | import Foundation 9 | import InstallationManager 10 | import SwiftUI 11 | import Common 12 | 13 | enum PreflightStatus { 14 | case failure(PreflightResponse) 15 | case success 16 | } 17 | 18 | enum InitStatus { 19 | case idle 20 | case downloading 21 | case success(VersionManifest) 22 | case failure(PreflightResponse) 23 | } 24 | 25 | enum LaunchStatus { 26 | case idle 27 | case starting(VersionManifest.VersionType, String) // Version, Message 28 | case running(VersionManifest.VersionType, Process) // Version, Process 29 | case failed(VersionManifest.VersionType, String) // Version, Message 30 | } 31 | 32 | @MainActor 33 | class AppState: ObservableObject { 34 | @Published 35 | var initializationStatus: InitStatus = .idle 36 | @Published 37 | var launchStatus: LaunchStatus = .idle 38 | 39 | @Published 40 | var launcherDirectory: URL? = nil 41 | @Published 42 | var minecraftDirectory: URL? = nil 43 | 44 | @State 45 | var javaDownload: Double = 0 46 | @State 47 | var libraryDownload: Double = 0 48 | @State 49 | var assetDownload: Double = 0 50 | 51 | @AppStorage("azure_refresh_token") 52 | var azureRefreshToken: String = "" 53 | @AppStorage("favorite-versions") 54 | var favoriteVersions: [VersionManifest.VersionType] = [.release] 55 | @AppStorage("selected-memory-allocation") 56 | var selectedMemoryAllocation: Int = 3 57 | 58 | @Published 59 | var credentials: SignInResult? = nil 60 | 61 | // Used for exporting versions 62 | @State 63 | var alertPresented = false 64 | @State 65 | var alertTitle = "" 66 | @State 67 | var alertMessage: String? = nil 68 | 69 | init() { 70 | 71 | } 72 | 73 | func setup() async { 74 | guard case .idle = initializationStatus else { 75 | return 76 | } 77 | 78 | let preflightStatus = await preflight() 79 | if case .failure(let result) = preflightStatus { 80 | self.initializationStatus = .failure(result) 81 | return 82 | } 83 | 84 | do { 85 | let manifest = try await VersionManifest.download(url: manifestUrl) 86 | self.initializationStatus = .success(manifest) 87 | } catch let err { 88 | print(err) 89 | // TODO: Improve this flow 90 | self.initializationStatus = .failure(PreflightResponse(message: err.localizedDescription)) 91 | } 92 | 93 | if let im = try? InstallationManager() { 94 | self.launcherDirectory = im.baseDirectory 95 | self.minecraftDirectory = im.gameDirectory 96 | } else { 97 | print("Failed to create InstallationManager") 98 | } 99 | } 100 | 101 | private func preflight() async -> PreflightStatus { 102 | let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String 103 | 104 | var preflightUrl = preflightUrl 105 | let queryItem = URLQueryItem(name: "app_version", value: appVersion) 106 | if #available(macOS 13.0, *) { 107 | preflightUrl.append(queryItems: [queryItem]) 108 | } else { 109 | var components = URLComponents(string: preflightUrl.absoluteString)! 110 | components.queryItems = [queryItem] 111 | preflightUrl = components.url! 112 | } 113 | 114 | let response = try? await retrieveData(from: preflightUrl) 115 | if let response = response { 116 | let data = response.0 117 | let decoded = try? JSONDecoder().decode(PreflightResponse.self, from: data) 118 | 119 | guard let decoded = decoded else { 120 | // Don't interrupt user if the message 121 | // fails to download or decode. 122 | return .success 123 | } 124 | 125 | return .failure(decoded) 126 | } else { 127 | return .success 128 | } 129 | } 130 | 131 | func runGame(version: VersionManifest.VersionTypeMetadataPair) async { 132 | let versionType = version.version 133 | let metadata = version.metadata 134 | 135 | print("Running \(metadata.id)") 136 | self.launchStatus = .starting(versionType, "") 137 | 138 | guard let credentials = credentials else { 139 | print("Not logged in") 140 | self.launchStatus = .failed(versionType, "Not logged in") 141 | return 142 | } 143 | 144 | do { 145 | self.launchStatus = .starting(versionType, "Initializing") 146 | let installationManager = try InstallationManager() 147 | 148 | print("Patching") 149 | self.launchStatus = .starting(versionType, "Patching for ARM") 150 | let patchInfo = try await VersionPatch.download(for: metadata.id) 151 | let package = try await metadata.package(with: patchInfo) 152 | guard package.minimumLauncherVersion >= 21 else { 153 | self.launchStatus = .failed(versionType, "Unfortunately, This utility does not work with versions prior to 1.13") 154 | return 155 | } 156 | 157 | print("Starting Java Download") 158 | self.launchStatus = .starting(versionType, "Starting Java Download") 159 | 160 | self.javaDownload = 0.10 161 | 162 | let clientJar = try await installationManager.downloadJar(for: package) 163 | self.javaDownload = 0.4 164 | 165 | let _ = try await installationManager.downloadJava(for: package) 166 | self.javaDownload = 1 167 | 168 | print("Starting Asset Download") 169 | self.launchStatus = .starting(versionType, "Starting Asset Download") 170 | try await installationManager.downloadAssets(for: package, with: patchInfo) { [weak self] progress in 171 | self?.assetDownload = progress 172 | } 173 | 174 | print("Starting Library Download") 175 | self.launchStatus = .starting(versionType, "Starting Library Download") 176 | let _ = try await installationManager.downloadLibraries(for: package) { [weak self] progress in 177 | self?.libraryDownload = progress 178 | } 179 | 180 | self.launchStatus = .starting(versionType, "Installing Natives") 181 | try installationManager.copyNatives() 182 | 183 | let launchArgumentsResults = try await installationManager.launchArguments( 184 | for: package, 185 | with: credentials, 186 | clientJar: clientJar, 187 | memory: UInt8(selectedMemoryAllocation) 188 | ) 189 | switch launchArgumentsResults { 190 | case .success(let args): 191 | // java 192 | let javaBundle = installationManager.javaBundle! 193 | let javaExec = javaBundle.appendingPathComponent("Contents/Home/bin/java", isDirectory: false) 194 | 195 | let proc = Process() 196 | proc.executableURL = javaExec 197 | proc.arguments = args 198 | proc.currentDirectoryURL = installationManager.baseDirectory 199 | proc.terminationHandler = { proc in 200 | DispatchQueue.main.async { 201 | self.launchStatus = .idle 202 | } 203 | } 204 | 205 | // let pipe = Pipe() 206 | // proc.standardOutput = pipe 207 | 208 | // print(javaExec.absoluteString) 209 | // print(args.joined(separator: " ")) 210 | // print(installationManager.baseDirectory.absoluteString) 211 | 212 | self.launchStatus = .starting(versionType, "Launching game") 213 | proc.launch() 214 | 215 | self.launchStatus = .running(versionType, proc) 216 | self.javaDownload = 0 217 | self.libraryDownload = 0 218 | self.assetDownload = 0 219 | case .failure(let error): 220 | self.launchStatus = .failed(versionType, error.localizedDescription) 221 | return 222 | } 223 | } catch let err { 224 | javaDownload = 0 225 | libraryDownload = 0 226 | assetDownload = 0 227 | 228 | if let cerr = err as? CError { 229 | switch cerr { 230 | case .networkError(let errorMessage): 231 | self.launchStatus = .failed(versionType, "Network Error: \(errorMessage)") 232 | case .encodingError(let errorMessage): 233 | self.launchStatus = .failed(versionType, "Encoding Error: \(errorMessage)") 234 | case .decodingError(let errorMessage): 235 | self.launchStatus = .failed(versionType, "Decoding Error: \(errorMessage)") 236 | case .filesystemError(let errorMessage): 237 | self.launchStatus = .failed(versionType, "Filesystem Error: \(errorMessage)") 238 | case .stateError(let errorMessage): 239 | self.launchStatus = .failed(versionType, "State Error: \(errorMessage)") 240 | case .sha1Error(let expected, let found): 241 | self.launchStatus = .failed(versionType, "SHA1 Mismatch: Expected \(expected) but found \(found)") 242 | case .unknownVersion(let version): 243 | self.launchStatus = .failed(versionType, "Unknown Version: \(version)") 244 | case .unknownError(let errorMessage): 245 | self.launchStatus = .failed(versionType, errorMessage) 246 | } 247 | } else { 248 | self.launchStatus = .failed(versionType, err.localizedDescription) 249 | } 250 | 251 | return 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /M1Craft/Array+RawRepresentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+RawRepresentable.swift 3 | // M1Craft 4 | // 5 | // Created by Ezekiel Elin on 4/6/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Array: RawRepresentable where Element: Codable { 11 | public init?(rawValue: String) { 12 | guard let data = rawValue.data(using: .utf8), 13 | let result = try? JSONDecoder().decode([Element].self, from: data) 14 | else { 15 | return nil 16 | } 17 | self = result 18 | } 19 | 20 | public var rawValue: String { 21 | guard let data = try? JSONEncoder().encode(self), 22 | let result = String(data: data, encoding: .utf8) 23 | else { 24 | return "[]" 25 | } 26 | return result 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /M1Craft/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /M1Craft/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon_16x16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "icon_16x16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "icon_32x32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "icon_32x32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "icon_128x128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "icon_128x128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "icon_256x256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "icon_256x256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "icon_512x512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "icon_512x512@2x.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /M1Craft/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezfe/m1craft/9d6fe1d229a6a39e94d02a61febb8a2c69107bd2/M1Craft/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /M1Craft/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezfe/m1craft/9d6fe1d229a6a39e94d02a61febb8a2c69107bd2/M1Craft/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /M1Craft/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezfe/m1craft/9d6fe1d229a6a39e94d02a61febb8a2c69107bd2/M1Craft/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /M1Craft/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezfe/m1craft/9d6fe1d229a6a39e94d02a61febb8a2c69107bd2/M1Craft/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /M1Craft/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezfe/m1craft/9d6fe1d229a6a39e94d02a61febb8a2c69107bd2/M1Craft/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /M1Craft/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezfe/m1craft/9d6fe1d229a6a39e94d02a61febb8a2c69107bd2/M1Craft/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /M1Craft/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezfe/m1craft/9d6fe1d229a6a39e94d02a61febb8a2c69107bd2/M1Craft/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /M1Craft/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezfe/m1craft/9d6fe1d229a6a39e94d02a61febb8a2c69107bd2/M1Craft/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /M1Craft/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezfe/m1craft/9d6fe1d229a6a39e94d02a61febb8a2c69107bd2/M1Craft/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /M1Craft/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezfe/m1craft/9d6fe1d229a6a39e94d02a61febb8a2c69107bd2/M1Craft/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /M1Craft/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /M1Craft/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // M1Craft UI 4 | // 5 | // Created by Ezekiel Elin on 11/15/21. 6 | // 7 | 8 | import Foundation 9 | 10 | //let serverAddress = URL(string: "http://localhost:8080/api")! 11 | let serverAddress = URL(string: "https://m1craft.ezekiel.dev/api")! 12 | 13 | let authStartUrl = serverAddress.appendingPathComponent("auth/start") 14 | let authRefreshUrl = serverAddress.appendingPathComponent("auth/refresh") 15 | let preflightUrl = serverAddress.appendingPathComponent("preflight") 16 | let manifestUrl = serverAddress.appendingPathComponent("manifest") 17 | -------------------------------------------------------------------------------- /M1Craft/Localization/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | M1Craft 4 | 5 | Created by Ezekiel Elin on 6/13/22. 6 | 7 | */ 8 | 9 | "Close" = "Close"; 10 | "OK" = "OK"; 11 | 12 | /* Settings */ 13 | "Open Settings" = "Open Settings"; 14 | "Settings" = "Settings"; 15 | "Memory Allocation: %lldGB" = "Memory Allocation: %lldGB"; 16 | "Check for Updates…" = "Check for Updates…"; 17 | "Currently signed in as: %@" = "Currently signed in as: %@"; 18 | "Currently signed out." = "Currently signed out."; 19 | "Sign In" = "Sign In"; 20 | "Sign out" = "Sign out"; 21 | 22 | "Complete login to continue." = "Complete login to continue."; 23 | "If no window appears, quit Safari and try again." = "If no window appears, quit Safari and try again."; 24 | 25 | /* Right-click a version */ 26 | "Export modified version JSON..." = "Export modified version JSON..."; 27 | "Add Favorite" = "Add Favorite"; 28 | "Remove Favorite" = "Remove Favorite"; 29 | 30 | "Loading..." = "Loading..."; 31 | "Retrieving Minecraft Credentials..." = "Retrieving Minecraft Credentials..."; 32 | "Authentication Error" = "Authentication Error"; 33 | 34 | "Open launcher directory" = "Open launcher directory"; 35 | "Open minecraft directory" = "Open minecraft directory"; 36 | 37 | "Play" = "Play"; 38 | "Starting..." = "Starting..."; 39 | "Running..." = "Running..."; 40 | "Stop" = "Stop"; 41 | 42 | /* Status updates when starting game */ 43 | "Java Libraries" = "Java Libraries"; 44 | "Java Runtime and Main Jar" = "Java Runtime and Main Jar"; 45 | "Assets" = "Assets"; 46 | -------------------------------------------------------------------------------- /M1Craft/M1Craft App.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M1Craft_UIApp.swift 3 | // M1Craft UI 4 | // 5 | // Created by Ezekiel Elin on 10/18/21. 6 | // 7 | 8 | import SwiftUI 9 | import InstallationManager 10 | 11 | @main 12 | struct M1CraftApp: App { 13 | @StateObject 14 | private var appState: AppState 15 | 16 | @StateObject 17 | var updaterViewModel = UpdaterViewModel() 18 | 19 | @MainActor 20 | init() { 21 | self._appState = StateObject(wrappedValue: AppState()) 22 | } 23 | 24 | var body: some Scene { 25 | WindowGroup { 26 | MainWindow() 27 | .environmentObject(appState) 28 | .frame(minWidth: 500, 29 | maxWidth: .infinity, 30 | minHeight: 350, 31 | maxHeight: .infinity) 32 | .alert(isPresented: $appState.alertPresented, content: { 33 | if let alertMessage = appState.alertMessage { 34 | return Alert(title: Text(appState.alertTitle), message: Text(alertMessage), dismissButton: nil) 35 | } else { 36 | return Alert(title: Text(appState.alertTitle), dismissButton: nil) 37 | } 38 | }) 39 | .onAppear { 40 | NSWindow.allowsAutomaticWindowTabbing = false 41 | Task { await appState.setup() } 42 | } 43 | } 44 | .commands { 45 | CommandGroup(after: .appInfo) { 46 | CheckForUpdatesView(updaterViewModel: updaterViewModel) 47 | } 48 | CommandGroup(after: .importExport) { 49 | Group { 50 | Button("Open launcher directory") { 51 | if let ld = appState.launcherDirectory { 52 | NSWorkspace.shared.open(ld) 53 | } 54 | }.disabled(appState.launcherDirectory == nil) 55 | Button("Open minecraft directory") { 56 | if let md = appState.minecraftDirectory { 57 | NSWorkspace.shared.open(md) 58 | } 59 | }.disabled(appState.minecraftDirectory == nil) 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /M1Craft/M1Craft-Debug.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.disable-library-validation 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /M1Craft/M1Craft.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /M1Craft/Views/AuthView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthView.swift 3 | // M1Craft UI 4 | // 5 | // Created by Ezekiel Elin on 11/11/21. 6 | // 7 | 8 | import Foundation 9 | import AuthenticationServices 10 | import SwiftUI 11 | import InstallationManager 12 | import Common 13 | 14 | class SignInViewModel: NSObject, ObservableObject, ASWebAuthenticationPresentationContextProviding { 15 | 16 | func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { 17 | return ASPresentationAnchor() 18 | } 19 | 20 | func signIn() async throws -> SignInResult { 21 | let sentState = UUID() 22 | 23 | let callbackUrl: URL = try await withCheckedThrowingContinuation { continuation in 24 | var components = URLComponents(url: authStartUrl, resolvingAgainstBaseURL: false)! 25 | components.queryItems = [URLQueryItem(name: "state", value: sentState.description)] 26 | let url = components.url! 27 | 28 | let authSession = ASWebAuthenticationSession( 29 | url: url, 30 | callbackURLScheme: "m1craft") { (url, error) in 31 | print("Callback...") 32 | if let url = url { 33 | continuation.resume(returning: url) 34 | } else if let error = error { 35 | continuation.resume(throwing: error) 36 | } else { 37 | print("Received no URL or error! Illegal state.") 38 | } 39 | } 40 | 41 | authSession.presentationContextProvider = self 42 | 43 | DispatchQueue.main.async { 44 | authSession.start() 45 | print("Started session...") 46 | } 47 | } 48 | 49 | let components = URLComponents(url: callbackUrl, resolvingAgainstBaseURL: false)! 50 | let queryItems = components.queryItems 51 | 52 | let resultBase64 = queryItems?.first(where: { $0.name == "signInResult" })?.value 53 | let error = queryItems?.first(where: { $0.name == "error_message" })?.value 54 | 55 | guard let resultBase64 = resultBase64 else { 56 | throw CError.unknownError(error ?? "Unknown error") 57 | } 58 | 59 | guard let data = Data(base64Encoded: resultBase64) else { 60 | throw CError.decodingError("Failed to decode base-64: \(resultBase64)") 61 | } 62 | 63 | do { 64 | let signInResult = try JSONDecoder().decode(SignInResult.self, from: data) 65 | UserDefaults.standard.set(signInResult.refresh, forKey: "azure_refresh_token") 66 | 67 | return signInResult 68 | } catch let err { 69 | throw CError.decodingError("Failed to decode SignInResult: \(err.localizedDescription)") 70 | } 71 | } 72 | } 73 | 74 | struct AuthView: View { 75 | @EnvironmentObject 76 | var appState: AppState 77 | 78 | @StateObject 79 | var viewModel = SignInViewModel() 80 | 81 | @State 82 | var signingIn = false 83 | @State 84 | var errorMessageShown = false 85 | @State 86 | var errorMessage = "" 87 | 88 | var body: some View { 89 | VStack { 90 | if signingIn { 91 | Text("Complete login to continue.") 92 | Text("If no window appears, quit Safari and try again.") 93 | ProgressView() 94 | } else { 95 | Button("Sign In") { 96 | Task { 97 | signingIn = true 98 | do { 99 | let res = try await viewModel.signIn() 100 | print("Received res; Saving...", res) 101 | appState.credentials = res 102 | } catch let error { 103 | if let asError = error as? ASWebAuthenticationSessionError { 104 | switch asError.code { 105 | case .canceledLogin: 106 | errorMessage = "Login cancelled." 107 | default: 108 | errorMessage = "An unknown error occurred. Please quit Safari and try again." 109 | } 110 | } else if let cError = error as? CError { 111 | errorMessage = cError.errorText 112 | } else { 113 | errorMessage = error.localizedDescription 114 | } 115 | errorMessageShown = true 116 | } 117 | signingIn = false 118 | } 119 | } 120 | } 121 | } 122 | .alert("Authentication Error", isPresented: $errorMessageShown, actions: { 123 | Button { 124 | errorMessageShown = false 125 | appState.credentials = nil 126 | } label: { 127 | Text("OK") 128 | } 129 | }, message: { 130 | Text(errorMessage) 131 | }) 132 | .padding() 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /M1Craft/Views/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // M1Craft UI 4 | // 5 | // Created by Ezekiel Elin on 10/17/21. 6 | // 7 | 8 | import SwiftUI 9 | import InstallationManager 10 | import Common 11 | 12 | extension VersionManifest.VersionType: RawRepresentable { 13 | public init?(rawValue: String) { 14 | if rawValue == "__release" { 15 | self = .release 16 | } else if rawValue == "__snapshot" { 17 | self = .snapshot 18 | } else { 19 | self = .custom(rawValue) 20 | } 21 | } 22 | 23 | public var rawValue: String { 24 | switch self { 25 | case .release: 26 | return "__release" 27 | case .snapshot: 28 | return "__snapshot" 29 | case .custom(let version): 30 | return version 31 | } 32 | } 33 | } 34 | 35 | struct ContentView: View { 36 | @EnvironmentObject 37 | var appState: AppState 38 | 39 | @State 40 | var availableVersions: [VersionManifest.VersionType] = [.release, .snapshot] 41 | 42 | @AppStorage("selected-memory-allocation") 43 | var selectedMemoryAllocation: Int = 3 44 | 45 | @State 46 | var startedDownloading = false 47 | 48 | var body: some View { 49 | VStack { 50 | Form { 51 | // Picker(selection: $selectedVersion, label: Text("Auto Version:")) { 52 | // Text("Latest Release").tag(VersionManifest.VersionType.release) 53 | // Text("Latest Snapshot").tag(VersionManifest.VersionType.snapshot) 54 | // } 55 | // .pickerStyle(.radioGroup) 56 | 57 | if !availableVersions.isEmpty { 58 | // Picker(selection: $selectedVersion, label: Text("Custom Version:")) { 59 | // ForEach(availableVersions, id: \.hashValue) { v in 60 | // switch v { 61 | // case .custom(let s): 62 | // Text(s).tag(v) 63 | // case .snapshot: 64 | // Text("Latest Snapshot").tag(v) 65 | // case .release: 66 | // Text("Latest Release").tag(v) 67 | // } 68 | // } 69 | // } 70 | // .scaledToFit() 71 | // .pickerStyle(.menu) 72 | } 73 | 74 | Button { 75 | // runGame() 76 | } label: { 77 | // switch selectedVersion { 78 | // case .custom(let v): 79 | // Text("Launch \(v)") 80 | // case .release: 81 | // Text("Launch Latest Release") 82 | // case .snapshot: 83 | // Text("Launch Latest Snapshot") 84 | // } 85 | Image(systemName: "play.circle") 86 | } 87 | .disabled(startedDownloading) 88 | } 89 | .disabled(startedDownloading) 90 | 91 | if startedDownloading { 92 | Divider() 93 | 94 | ProgressView("Java Runtime and Main Jar", value: appState.javaDownload) 95 | ProgressView("Java Libraries", value: appState.libraryDownload) 96 | ProgressView("Assets", value: appState.assetDownload) 97 | } 98 | 99 | // if let message = message, !message.contains("Starting game") { 100 | // Divider() 101 | // Text(message) 102 | // Text("If a network/time-out error occurred, simply re-start the game. It will resume where it left off") 103 | // } 104 | } 105 | .padding() 106 | .task { 107 | do { 108 | let manifest = try await VersionManifest.download(url: manifestUrl) 109 | if let _earliestReleaseTime = manifest.versions.first(where: { $0.id == "19w11a" })?.releaseTime { 110 | availableVersions = [.release, .snapshot] 111 | let newVersions = manifest.versions 112 | .filter { $0.releaseTime >= _earliestReleaseTime } 113 | .map { VersionManifest.VersionType.custom($0.id) } 114 | availableVersions.append(contentsOf: newVersions) 115 | } 116 | } catch let err { 117 | print(err.localizedDescription) 118 | } 119 | } 120 | } 121 | } 122 | 123 | //struct ContentView_Previews: PreviewProvider { 124 | // static var previews: some View { 125 | // ContentView( 126 | // credentials: SignInResult( 127 | // id: "uuidhere", 128 | // name: "ezfe", 129 | // token: "123456.67890", 130 | // refresh: "962312.134134" 131 | // ), 132 | // launcherDirectory: .constant(nil), 133 | // minecraftDirectory: .constant(nil) 134 | // ) 135 | // } 136 | //} 137 | -------------------------------------------------------------------------------- /M1Craft/Views/MainWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainWindow.swift 3 | // M1Craft 4 | // 5 | // Created by Ezekiel Elin on 2/23/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct MainWindow: View { 11 | @EnvironmentObject 12 | var appState: AppState 13 | 14 | @State 15 | var viewSettings = false 16 | @State 17 | var loginSheet = true 18 | 19 | var body: some View { 20 | Preflight() 21 | .environmentObject(appState) 22 | .toolbar { 23 | ToolbarItemGroup(placement: .status) { 24 | Button { 25 | viewSettings = true 26 | } label: { 27 | Label("Settings", systemImage: "gearshape") 28 | } 29 | .help("Open Settings") 30 | } 31 | } 32 | .sheet(isPresented: $viewSettings) { 33 | VStack { 34 | SettingsView() 35 | HStack { 36 | Spacer() 37 | Button("Close") { 38 | viewSettings = false 39 | } 40 | .keyboardShortcut(.cancelAction) 41 | } 42 | } 43 | .padding() 44 | } 45 | } 46 | } 47 | 48 | struct MainWindow_Previews: PreviewProvider { 49 | static var previews: some View { 50 | MainWindow() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /M1Craft/Views/Preflight.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Preflight.swift 3 | // M1Craft UI 4 | // 5 | // Created by Ezekiel Elin on 11/14/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct PreflightResponse: Decodable { 11 | var message: String 12 | var url: URL? 13 | } 14 | 15 | struct Preflight: View { 16 | @EnvironmentObject 17 | var appState: AppState 18 | 19 | var body: some View { 20 | switch appState.initializationStatus { 21 | case .idle, .downloading: 22 | Text("Loading...") 23 | ProgressView() 24 | case .failure(let result): 25 | Text(result.message) 26 | if let url = result.url { 27 | Link("Continue...", destination: url) 28 | } 29 | case .success(let manifest): 30 | if appState.credentials != nil { 31 | VersionListView( 32 | selectedVersionId: .constant(""), 33 | manifest: manifest 34 | ) 35 | .environmentObject(appState) 36 | } else if appState.azureRefreshToken.count > 0 { 37 | RefreshAuthView() 38 | .environmentObject(appState) 39 | } else { 40 | AuthView() 41 | .environmentObject(appState) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /M1Craft/Views/RefreshAuthView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthView.swift 3 | // M1Craft UI 4 | // 5 | // Created by Ezekiel Elin on 11/11/21. 6 | // 7 | 8 | import Foundation 9 | import AuthenticationServices 10 | import SwiftUI 11 | import InstallationManager 12 | import Common 13 | 14 | struct RefreshAuthView: View { 15 | @EnvironmentObject 16 | var appState: AppState 17 | 18 | @State 19 | var signingIn = false 20 | @State 21 | var message = "" 22 | 23 | var body: some View { 24 | VStack { 25 | if signingIn { 26 | Text("Retrieving Minecraft Credentials...") 27 | ProgressView() 28 | } else { 29 | Text(message) 30 | } 31 | } 32 | .onAppear(perform: { 33 | signingIn = true 34 | print("Attempting refresh for \(appState.azureRefreshToken)") 35 | Task { 36 | var refreshUrl = authRefreshUrl 37 | let queryItems = [URLQueryItem(name: "refreshToken", value: appState.azureRefreshToken)] 38 | 39 | if #available(macOS 13.0, *) { 40 | refreshUrl.append(queryItems: queryItems) 41 | } else { 42 | var components = URLComponents(url: refreshUrl, resolvingAgainstBaseURL: false) 43 | components?.queryItems = queryItems 44 | guard let _refreshUrl = components?.url else { 45 | print("Failed to build refresh token. Resetting.") 46 | appState.azureRefreshToken = "" 47 | return 48 | } 49 | refreshUrl = _refreshUrl 50 | } 51 | 52 | let request = URLRequest(url: refreshUrl) 53 | let (data, response) = try await retrieveData(for: request) 54 | 55 | let code = (response as? HTTPURLResponse)?.statusCode 56 | 57 | if let code = code { 58 | do { 59 | if code == 200 { 60 | let results = try JSONDecoder().decode(SignInResult.self, from: data) 61 | appState.azureRefreshToken = results.refresh 62 | appState.credentials = results 63 | } else { 64 | let response = try JSONDecoder().decode(PreflightResponse.self, from: data) 65 | message = response.message 66 | } 67 | } catch let err { 68 | message = err.localizedDescription 69 | } 70 | } else { 71 | message = "An unexpected error occurred." 72 | } 73 | 74 | signingIn = false 75 | } 76 | }) 77 | .padding() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /M1Craft/Views/SettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsView.swift 3 | // M1Craft 4 | // 5 | // Created by Ezekiel Elin on 1/20/22. 6 | // 7 | 8 | import SwiftUI 9 | import InstallationManager 10 | 11 | struct SettingsView: View { 12 | @EnvironmentObject 13 | var appState: AppState 14 | 15 | var body: some View { 16 | VStack { 17 | if let credentials = appState.credentials { 18 | Text("Currently signed in as: \(credentials.name)") 19 | Button("Sign out") { 20 | appState.azureRefreshToken = "" 21 | appState.credentials = nil 22 | } 23 | } else { 24 | Text("Currently signed out.") 25 | } 26 | Divider() 27 | 28 | Stepper(value: $appState.selectedMemoryAllocation, 29 | in: 1...16, 30 | step: 1) { 31 | Text("Memory Allocation: \(appState.selectedMemoryAllocation)GB") 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /M1Craft/Views/SparkleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sparkle.swift 3 | // M1Craft UI 4 | // 5 | // Created by Ezekiel Elin on 1/9/22. 6 | // 7 | 8 | import SwiftUI 9 | import Sparkle 10 | 11 | // This view model class manages Sparkle's updater and publishes when new updates are allowed to be checked 12 | final class UpdaterViewModel: ObservableObject { 13 | private let updaterController: SPUStandardUpdaterController 14 | 15 | @Published 16 | var canCheckForUpdates = false 17 | 18 | init() { 19 | // If you want to start the updater manually, pass false to startingUpdater and call .startUpdater() later 20 | // This is where you can also pass an updater delegate if you need one 21 | updaterController = SPUStandardUpdaterController(startingUpdater: true, 22 | updaterDelegate: nil, 23 | userDriverDelegate: nil) 24 | 25 | updaterController.updater.publisher(for: \.canCheckForUpdates) 26 | .assign(to: &$canCheckForUpdates) 27 | } 28 | 29 | func checkForUpdates() { 30 | updaterController.checkForUpdates(nil) 31 | } 32 | } 33 | 34 | // This additional view is needed for the disabled state on the menu item to work properly before Monterey. 35 | // See https://stackoverflow.com/questions/68553092/menu-not-updating-swiftui-bug for more information 36 | struct CheckForUpdatesView: View { 37 | @ObservedObject var updaterViewModel: UpdaterViewModel 38 | 39 | var body: some View { 40 | Button("Check for Updates…", action: updaterViewModel.checkForUpdates) 41 | .disabled(!updaterViewModel.canCheckForUpdates) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /M1Craft/Views/VersionList/VersionListRowView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VersionListRowView.swift 3 | // M1Craft 4 | // 5 | // Created by Ezekiel Elin on 2/23/22. 6 | // 7 | 8 | import SwiftUI 9 | import InstallationManager 10 | 11 | struct VersionListRowView: View { 12 | let versionMetadataPair: VersionManifest.VersionTypeMetadataPair 13 | var version: VersionManifest.VersionType { versionMetadataPair.version } 14 | var metadata: VersionManifest.VersionMetadata { versionMetadataPair.metadata } 15 | let favorite: Bool 16 | let selected: Bool 17 | @EnvironmentObject 18 | var appState: AppState 19 | 20 | var body: some View { 21 | HStack { 22 | Button { 23 | if favorite { 24 | appState.favoriteVersions.removeAll(where: { $0 == version }) 25 | } else { 26 | appState.favoriteVersions.append(version) 27 | } 28 | } label: { 29 | if favorite { 30 | Image(systemName: "star.fill") 31 | .foregroundColor(.yellow) 32 | } else { 33 | Image(systemName: "star") 34 | } 35 | } 36 | .buttonStyle(.borderless) 37 | VStack(alignment: .leading) { 38 | switch version { 39 | case .release: 40 | Text(verbatim: "Latest Release (\(metadata.id))") 41 | .font(.body) 42 | case .snapshot: 43 | Text(verbatim: "Latest Snapshot (\(metadata.id))") 44 | .font(.body) 45 | case .custom(_): 46 | Text(verbatim: "\(metadata.id)") 47 | .font(.body) 48 | } 49 | Text(verbatim: metadata.releaseTime.formatted()) 50 | .font(.caption) 51 | } 52 | Spacer() 53 | switch appState.launchStatus { 54 | case .idle: 55 | playControl() 56 | case .failed(let version, let message): 57 | if version == self.version { 58 | Text(message) 59 | } 60 | playControl() 61 | case .starting(let version, let message): 62 | if version == self.version { 63 | Text(message) 64 | Button("Starting...", action: { () in }) 65 | .buttonStyle(AppStoreButtonStyle(primary: true, highlighted: selected)) 66 | .disabled(true) 67 | } else { 68 | EmptyView() 69 | } 70 | case .running(let version, let process): 71 | if version == self.version { 72 | Button("Running...", action: { () in }) 73 | .buttonStyle(AppStoreButtonStyle(primary: true, highlighted: selected)) 74 | .disabled(true) 75 | Button { 76 | process.terminate() 77 | } label: { 78 | Label("Stop", systemImage: "xmark.circle") 79 | } 80 | .buttonStyle(AppStoreButtonStyle(primary: true, highlighted: selected)) 81 | 82 | } else { 83 | EmptyView() 84 | } 85 | } 86 | } 87 | .contextMenu { 88 | Button(action: self.playAction) { 89 | Label("Play", systemImage: "play.circle") 90 | } 91 | if favorite { 92 | Button() { 93 | appState.favoriteVersions.removeAll(where: { $0 == version }) 94 | } label: { 95 | Label("Remove Favorite", systemImage: "star.slash") 96 | } 97 | } else { 98 | Button() { 99 | appState.favoriteVersions.append(version) 100 | } label: { 101 | Label("Add Favorite", systemImage: "star") 102 | } 103 | } 104 | 105 | Divider() 106 | 107 | Button("Export modified version JSON...") { 108 | let savePanel = NSSavePanel() 109 | savePanel.allowedContentTypes = [.json] 110 | savePanel.canCreateDirectories = true 111 | savePanel.isExtensionHidden = false 112 | savePanel.allowsOtherFileTypes = false 113 | savePanel.title = "Save Version JSON" 114 | savePanel.directoryURL = appState.minecraftDirectory?.appendingPathComponent("versions") 115 | savePanel.nameFieldLabel = "File name:" 116 | 117 | let response = savePanel.runModal() 118 | 119 | appState.alertTitle = "Preparing JSON..." 120 | appState.alertPresented = true 121 | 122 | if let url = savePanel.url, response == .OK { 123 | Task { 124 | do { 125 | let patchInfo = try await VersionPatch.download(for: metadata.id) 126 | let package = try await metadata.package(with: patchInfo) 127 | 128 | let encoder = JSONEncoder() 129 | encoder.dateEncodingStrategy = .iso8601 130 | let data = try encoder.encode(package) 131 | 132 | try data.write(to: url) 133 | print("Saved json...") 134 | 135 | appState.alertTitle = "Saved JSON" 136 | } catch let err { 137 | appState.alertTitle = "Failed to save JSON" 138 | appState.alertMessage = err.localizedDescription 139 | } 140 | } 141 | } else { 142 | appState.alertPresented = false 143 | } 144 | } 145 | 146 | } 147 | } 148 | 149 | @ViewBuilder 150 | private func playControl() -> some View { 151 | Button("Play", action: self.playAction) 152 | .buttonStyle(AppStoreButtonStyle(primary: true, highlighted: selected)) 153 | .help("Play \(metadata.id)") 154 | } 155 | 156 | func playAction() { 157 | Task { 158 | await appState.runGame(version: versionMetadataPair) 159 | } 160 | } 161 | } 162 | 163 | /* 164 | struct VersionListRowView_Previews: PreviewProvider { 165 | static var previews: some View { 166 | VersionListRowView( 167 | version: .custom("1.19.1"), 168 | metadata: .init(id: "1.19.1", type: "snapshot", time: Date(), releaseTime: Date(), url: "https://mojang.com/version1.json", sha1: "123sha456"), 169 | favorite: true, 170 | selected: false 171 | ) 172 | VersionListRowView( 173 | version: .release, 174 | metadata: .init(id: "1.19.2", type: "snapshot", time: Date(), releaseTime: Date(), url: "https://mojang.com/version2.json", sha1: "123sha456"), 175 | favorite: false, 176 | selected: true 177 | ) 178 | } 179 | } 180 | */ 181 | -------------------------------------------------------------------------------- /M1Craft/Views/VersionList/VersionListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VersionListView.swift 3 | // M1Craft 4 | // 5 | // Created by Ezekiel Elin on 2/23/22. 6 | // 7 | 8 | import SwiftUI 9 | import InstallationManager 10 | 11 | struct VersionListView: View { 12 | @EnvironmentObject 13 | var appState: AppState 14 | @Binding 15 | var selectedVersionId: VersionManifest.VersionMetadata.ID? 16 | var manifest: VersionManifest 17 | 18 | var versions: [VersionManifest.VersionTypeMetadataPair] { 19 | return manifest.versionTypes.sorted(by: { v1, v2 in 20 | let v1Fav = appState.favoriteVersions.contains(v1.version) 21 | let v2Fav = appState.favoriteVersions.contains(v2.version) 22 | if v1Fav && !v2Fav { 23 | return true 24 | } else if v2Fav && !v1Fav { 25 | return false 26 | } else { 27 | switch v1.version { 28 | case .custom(_): 29 | switch v2.version { 30 | case .custom(_): 31 | return v1.metadata.releaseTime > v2.metadata.releaseTime 32 | default: 33 | return false 34 | } 35 | 36 | default: 37 | switch v2.version { 38 | case .custom(_): 39 | return v1.metadata.releaseTime > v2.metadata.releaseTime 40 | default: 41 | return true 42 | } 43 | } 44 | } 45 | }) 46 | } 47 | 48 | init(selectedVersionId: Binding, manifest: VersionManifest) { 49 | self._selectedVersionId = selectedVersionId 50 | self.manifest = manifest 51 | } 52 | 53 | var body: some View { 54 | VStack { 55 | List(self.versions, selection: $selectedVersionId) { versionPair in 56 | VersionListRowView( 57 | versionMetadataPair: versionPair, 58 | favorite: appState.favoriteVersions.contains(versionPair.version), 59 | selected: selectedVersionId == versionPair.metadata.id 60 | ) 61 | .environmentObject(appState) 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /M1Craft/Views/Xcodes/AppStoreButtonStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppStoreButtonStyle.swift 3 | // Xcodes 4 | // 5 | // Created by RobotsAndPencils 6 | // https://github.com/RobotsAndPencils/XcodesApp/blob/main/Xcodes/Frontend/XcodeList/AppStoreButtonStyle.swift 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct AppStoreButtonStyle: ButtonStyle { 12 | var primary: Bool 13 | var highlighted: Bool 14 | 15 | private struct AppStoreButton: View { 16 | @SwiftUI.Environment(\.isEnabled) var isEnabled 17 | var configuration: ButtonStyle.Configuration 18 | var primary: Bool 19 | var highlighted: Bool 20 | 21 | var textColor: Color { 22 | if isEnabled { 23 | if primary { 24 | if highlighted { 25 | return Color.accentColor 26 | } 27 | else { 28 | return Color.white 29 | } 30 | } 31 | else { 32 | return Color.accentColor 33 | } 34 | } else { 35 | if primary { 36 | if highlighted { 37 | return Color(.disabledControlTextColor) 38 | } 39 | else { 40 | return Color.white 41 | } 42 | } 43 | else { 44 | if highlighted { 45 | return Color.white 46 | } 47 | else { 48 | return Color(.disabledControlTextColor) 49 | } 50 | } 51 | } 52 | } 53 | 54 | func background(isPressed: Bool) -> some View { 55 | Group { 56 | if isEnabled { 57 | if primary { 58 | Capsule() 59 | .fill( 60 | highlighted ? 61 | Color.white : 62 | Color.accentColor 63 | ) 64 | .brightness(isPressed ? -0.25 : 0) 65 | } else { 66 | Capsule() 67 | .fill( 68 | Color(NSColor(red: 0.95, green: 0.95, blue: 0.97, alpha: 1)) 69 | ) 70 | .brightness(isPressed ? -0.25 : 0) 71 | } 72 | } else { 73 | if primary { 74 | Capsule() 75 | .fill( 76 | highlighted ? 77 | Color.white : 78 | Color(.disabledControlTextColor) 79 | ) 80 | .brightness(isPressed ? -0.25 : 0) 81 | } else { 82 | EmptyView() 83 | } 84 | } 85 | } 86 | } 87 | var body: some View { 88 | configuration.label 89 | .font(Font.caption.weight(.bold)) 90 | .foregroundColor(textColor) 91 | .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) 92 | .frame(minWidth: 65) 93 | .background(background(isPressed: configuration.isPressed)) 94 | .padding(1) 95 | } 96 | } 97 | 98 | func makeBody(configuration: ButtonStyle.Configuration) -> some View { 99 | AppStoreButton(configuration: configuration, primary: primary, highlighted: highlighted) 100 | } 101 | } 102 | // 103 | //struct AppStoreButtonStyle_Previews: PreviewProvider { 104 | // static var previews: some View { 105 | // Group { 106 | // ForEach([ColorScheme.light, ColorScheme.dark], id: \.self) { colorScheme in 107 | // Group { 108 | // Button("OPEN", action: {}) 109 | // .buttonStyle(AppStoreButtonStyle(primary: true, highlighted: false)) 110 | // .padding() 111 | // .background(Color(.textBackgroundColor)) 112 | // .previewDisplayName("Primary") 113 | // Button("OPEN", action: {}) 114 | // .buttonStyle(AppStoreButtonStyle(primary: true, highlighted: true)) 115 | // .padding() 116 | // .background(Color(.controlAccentColor)) 117 | // .previewDisplayName("Primary, Highlighted") 118 | // Button("OPEN", action: {}) 119 | // .buttonStyle(AppStoreButtonStyle(primary: true, highlighted: false)) 120 | // .padding() 121 | // .disabled(true) 122 | // .background(Color(.textBackgroundColor)) 123 | // .previewDisplayName("Primary, Disabled") 124 | // Button("INSTALL", action: {}) 125 | // .buttonStyle(AppStoreButtonStyle(primary: false, highlighted: false)) 126 | // .padding() 127 | // .background(Color(.textBackgroundColor)) 128 | // .previewDisplayName("Secondary") 129 | // Button("INSTALL", action: {}) 130 | // .buttonStyle(AppStoreButtonStyle(primary: false, highlighted: true)) 131 | // .padding() 132 | // .background(Color(.controlAccentColor)) 133 | // .previewDisplayName("Secondary, Highlighted") 134 | // Button("INSTALL", action: {}) 135 | // .buttonStyle(AppStoreButtonStyle(primary: false, highlighted: false)) 136 | // .padding() 137 | // .disabled(true) 138 | // .background(Color(.textBackgroundColor)) 139 | // .previewDisplayName("Secondary, Disabled") 140 | // } 141 | // .environment(\.colorScheme, colorScheme) 142 | // } 143 | // } 144 | // } 145 | //} 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # m1craft 2 | 3 | # Notice: With full Apple Silicon support in the default launcher, this project is not receiving full priority. As such I will not be able to guarantee any technical support or ongoing compatibility, although it is my goal to keep it working and continue improving it in the future. 4 | 5 | # I recommend [Prism Launcher](https://prismlauncher.org) for an alternative launcher at this time 6 | 7 | Run Minecraft 1.17, 1.18, and 1.19 without installing any profiles, json files, or jars. 8 | 9 | This tool does **not** modify either the Minecraft JAR or LWJGL JARs. For 1.19, the LWJGL jars are provided by Mojang. For versions 1.18 and older and the LWJGL jars are pre-built 3.3.0 (3.3.1 for 22w16a and later) directly from lwjgl.org. 10 | 11 | Download: https://m1craft.ezekiel.dev 12 | 13 | **Warning:** For 1.18 and earlier, do not use the Minecraft fullscreen option, instead use the system one (green button). If you've used the Minecraft one and it's crashing, follow the steps [here](https://github.com/ezfe/m1craft/issues/5#issuecomment-972287174) 14 | 15 | Screen Shot 2022-06-11 at 4 58 00 PM 16 | 17 | Note: 1.18 and later can be run directly from the official Minecraft launcher. Follow instructions [here](https://gist.github.com/ezfe/8bc43a65e16b79c955f81b4d7fa4ae6a) if you'd prefer to do this instead – including if you need to mod the game. However this launcher is still easier (no install steps needed). 18 | -------------------------------------------------------------------------------- /readme-fr.md: -------------------------------------------------------------------------------- 1 | # m1craft 2 | 3 | Exécutez Minecraft 1.17, 1.18, et 1.19 sans installer de profils, de fichiers json, ou de jars. 4 | 5 | Cet outil ne modifie **pas** le JAR de Minecraft ou les JARs de LWJGL. Pour la version 1.19, les jars LWJGL sont fournis par Mojang. Pour les versions 1.18 et plus anciennes, les jars LWJGL sont pré-construits 3.3.0 (3.3.1 pour 22w16a et plus) directement depuis lwjgl.org. 6 | 7 | Téléchargement : https://m1craft.ezekiel.dev 8 | 9 | **Avertissement:** Pour les versions 1.18 et antérieures, n'utilisez pas l'option plein écran de Minecraft, mais plutôt celle du système (bouton vert). Si vous avez utilisé celle de Minecraft et qu'elle plante, suivez les étapes [ici](https://github.com/ezfe/m1craft/issues/5#issuecomment-972287174). 10 | 11 | Screen Shot 2022-06-11 at 4 58 00 PM 12 | 13 | Remarque : les versions 1.18 et ultérieures peuvent être exécutées directement à partir du lanceur officiel de Minecraft. Suivez les instructions [ici](https://gist.github.com/ezfe/8bc43a65e16b79c955f81b4d7fa4ae6a) si vous préférez le faire - y compris si vous avez besoin de modifier le jeu. Cependant, ce lanceur est toujours plus facile (aucune étape d'installation nécessaire). 14 | --------------------------------------------------------------------------------