├── .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 |
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 |
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 |
--------------------------------------------------------------------------------