Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/PojavLauncher_iOS
Path: blob/main/Natives/LauncherPrefManageJREViewController.m
589 views
1
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
2
#import "WFWorkflowProgressView.h"
3
#import "LauncherNavigationController.h"
4
#import "LauncherPreferences.h"
5
#import "LauncherPrefManageJREViewController.h"
6
#import "NSFileManager+NRFileManager.h"
7
#import "UIKit+hook.h"
8
#import "ios_uikit_bridge.h"
9
#import "utils.h"
10
11
#include <dlfcn.h>
12
#include <lzma.h>
13
#include <objc/runtime.h>
14
15
// 0 is reserved for default pickers
16
// INT_MAX is reserved for invalid runtimes
17
#define DEFAULT_JRE 0
18
#define INVALID_JRE INT_MAX
19
20
// https://www.gnu.org/software/tar/manual/html_node/Standard.html
21
typedef struct {
22
char name[100];
23
char mode[8];
24
char unused1[8+8];
25
char size[12];
26
char unused2[12+8];
27
char typeflag;
28
char linkname[100];
29
char unused3[6+2+32+32+8+8+155+12];
30
} TarHeader;
31
32
static WFWorkflowProgressView* currentProgressView;
33
34
@interface LauncherPrefManageJREViewController ()<UIContextMenuInteractionDelegate, UIDocumentPickerDelegate>
35
@property(nonatomic) NSMutableDictionary<NSNumber *, NSMutableArray *> *javaRuntimes;
36
@property(nonatomic) NSMutableArray<NSNumber *> *sortedJavaVersions;
37
@property(nonatomic) NSArray<NSString *> *selectedRTTags;
38
@property(nonatomic) NSMutableDictionary<NSString *, NSString *> *selectedRuntimes;
39
@property(nonatomic) UIMenu* currentMenu;
40
@property(nonatomic, weak) NSIndexPath* installingIndexPath;
41
@end
42
43
@implementation LauncherPrefManageJREViewController
44
45
+ (LauncherPrefManageJREViewController *)currentInstance {
46
UISplitViewController *splitVC = (id)currentVC();
47
LauncherNavigationController *nav = (id)splitVC.viewControllers[1];
48
if (![nav.topViewController isKindOfClass:LauncherPrefManageJREViewController.class]) {
49
return nil;
50
}
51
return (id)nav.topViewController;
52
}
53
54
- (void)viewDidLoad
55
{
56
[super viewDidLoad];
57
[self setTitle:localize(@"preference.title.manage_runtime", nil)];
58
59
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"plus"] style:UIBarButtonItemStylePlain target:self action:@selector(actionImportRuntime)];
60
61
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped];
62
self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive;
63
64
self.javaRuntimes = @{
65
@(DEFAULT_JRE): @[@"preference.manage_runtime.default.1165", @"preference.manage_runtime.default.117", @"launcher.menu.execute_jar"]
66
}.mutableCopy;
67
self.sortedJavaVersions = @[@(DEFAULT_JRE)].mutableCopy;
68
69
self.selectedRTTags = @[@"1_16_5_older", @"1_17_newer", @"execute_jar"];
70
self.selectedRuntimes = getPrefObject(@"java.java_homes");
71
72
NSString *internalPath = [NSString stringWithFormat:@"%@/java_runtimes", NSBundle.mainBundle.bundlePath];
73
NSString *externalPath = [NSString stringWithFormat:@"%s/java_runtimes", getenv("POJAV_HOME")];
74
[self listJREInPath:internalPath markInternal:YES];
75
[self listJREInPath:externalPath markInternal:NO];
76
77
// Load WFWorkflowProgressView
78
dlopen("/System/Library/PrivateFrameworks/WorkflowUIServices.framework/WorkflowUIServices", RTLD_GLOBAL);
79
}
80
81
+ (void)actionCancelImportRuntime {
82
UISplitViewController *splitVC = (id)currentVC();
83
LauncherNavigationController *nav = (id)splitVC.viewControllers[1];
84
[nav.progressViewMain.observedProgress cancel];
85
}
86
87
- (void)actionImportRuntime {
88
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc]
89
initForOpeningContentTypes:@[[UTType typeWithMIMEType:@"application/x-xz"]]];
90
documentPicker.delegate = self;
91
documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
92
[self presentViewController:documentPicker animated:YES completion:nil];
93
}
94
95
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url {
96
LauncherNavigationController *nav = (id)self.navigationController;
97
[nav setInteractionEnabled:NO forDownloading:NO];
98
99
[url startAccessingSecurityScopedResource];
100
NSUInteger xzSize = [NSFileManager.defaultManager attributesOfItemAtPath:url.path error:nil].fileSize;
101
102
NSProgress *totalProgress = [NSProgress progressWithTotalUnitCount:xzSize];
103
NSProgress *fileProgress = [NSProgress progressWithTotalUnitCount:0];
104
nav.progressViewMain.observedProgress = totalProgress;
105
nav.progressViewSub.observedProgress = fileProgress;
106
107
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
108
109
NSString *outPath = [NSString stringWithFormat:@"%s/java_runtimes/%@", getenv("POJAV_HOME"),
110
[url.path substringToIndex:url.path.length-7].lastPathComponent];
111
NSString *error = [LauncherPrefManageJREViewController extractTarXZ:url.path
112
to:outPath progress:totalProgress fileProgress:fileProgress
113
fileCallback:^(NSString* name) {
114
NSString *completedSize = [NSByteCountFormatter stringFromByteCount:fileProgress.completedUnitCount countStyle:NSByteCountFormatterCountStyleMemory];
115
NSString *totalSize = [NSByteCountFormatter stringFromByteCount:fileProgress.totalUnitCount countStyle:NSByteCountFormatterCountStyleMemory];
116
nav.progressText.text = [NSString stringWithFormat:@"(%@ / %@) %@", completedSize, totalSize, name];
117
currentProgressView.fractionCompleted = totalProgress.fractionCompleted;
118
}];
119
[url stopAccessingSecurityScopedResource];
120
121
if (error) {
122
showDialog(localize(@"Error", nil), error);
123
}
124
125
dispatch_async(dispatch_get_main_queue(), ^{
126
LauncherPrefManageJREViewController *vc = LauncherPrefManageJREViewController.currentInstance;
127
currentProgressView = nil;
128
129
if ((error || totalProgress.cancelled) && vc.installingIndexPath) {
130
[vc removeRuntimeAtIndexPath:vc.installingIndexPath];
131
} else {
132
NSNumber *version = self.sortedJavaVersions[vc.installingIndexPath.section];
133
NSString *name = self.javaRuntimes[version][vc.installingIndexPath.row];
134
objc_setAssociatedObject(name, @"installing", @(NO), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
135
}
136
vc.installingIndexPath = nil;
137
[vc.tableView reloadData];
138
139
[nav setInteractionEnabled:YES forDownloading:NO];
140
nav.progressViewMain.observedProgress = nil;
141
nav.progressViewSub.observedProgress = nil;
142
nav.progressText.text = @"";
143
});
144
});
145
}
146
147
#pragma mark Table view
148
149
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
150
return self.sortedJavaVersions.count;
151
}
152
153
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
154
NSNumber *version = self.sortedJavaVersions[section];
155
switch (self.sortedJavaVersions[section].intValue) {
156
case DEFAULT_JRE: return localize(@"preference.manage_runtime.header.default", nil);
157
case INVALID_JRE: return localize(@"preference.manage_runtime.header.invalid", nil);
158
default: return [NSString stringWithFormat:@"Java %@", self.sortedJavaVersions[section]];
159
}
160
}
161
162
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
163
switch (self.sortedJavaVersions[section].intValue) {
164
case DEFAULT_JRE: return localize(@"preference.manage_runtime.footer.default", nil);
165
case 8: return localize(@"preference.manage_runtime.footer.java8", nil);
166
case 17: return localize(@"preference.manage_runtime.footer.java17", nil);
167
default: return nil;
168
}
169
}
170
171
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
172
return self.javaRuntimes[self.sortedJavaVersions[section]].count;
173
}
174
175
- (UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
176
{
177
switch (indexPath.section) {
178
case 0: return [self tableView:tableView cellForDefaultRowAtIndexPath:indexPath];
179
default: return [self tableView:tableView cellForRuntimeRowAtIndexPath:indexPath];
180
}
181
}
182
183
- (UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForDefaultRowAtIndexPath:(NSIndexPath *)indexPath
184
{
185
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"DefaultCell"];
186
if (cell == nil) {
187
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"DefaultCell"];
188
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
189
}
190
cell.textLabel.text = localize(self.javaRuntimes[@DEFAULT_JRE][indexPath.row], nil);
191
cell.detailTextLabel.text = [NSString stringWithFormat:@"Java %@",
192
((NSDictionary *)self.selectedRuntimes[@"0"])[self.selectedRTTags[indexPath.row]]];
193
return cell;
194
}
195
196
- (UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRuntimeRowAtIndexPath:(NSIndexPath *)indexPath {
197
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"RTCell"];
198
if (cell == nil) {
199
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"RTCell"];
200
}
201
NSNumber *version = self.sortedJavaVersions[indexPath.section];
202
NSString *name = self.javaRuntimes[version][indexPath.row];
203
BOOL isInternal = [objc_getAssociatedObject(name, @"internalJRE") boolValue];
204
BOOL isInstalling = [objc_getAssociatedObject(name, @"installing") boolValue];
205
if (isInternal) {
206
cell.textLabel.text = [NSString stringWithFormat:@"[Internal] %@", name];
207
} else {
208
cell.textLabel.text = name;
209
}
210
211
if (isInstalling) {
212
self.installingIndexPath = indexPath;
213
214
LauncherNavigationController *nav = (id)self.navigationController;
215
if (!currentProgressView) {
216
currentProgressView = [[NSClassFromString(@"WFWorkflowProgressView") alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
217
currentProgressView.resolvedTintColor = self.view.tintColor;
218
[currentProgressView addTarget:LauncherPrefManageJREViewController.class
219
action:@selector(actionCancelImportRuntime) forControlEvents:UIControlEventPrimaryActionTriggered];
220
}
221
222
cell.accessoryView = currentProgressView;
223
} else {
224
cell.accessoryView = nil;
225
}
226
227
// Set checkmark; with internal runtime it's a bit tricky to check
228
if ([self.selectedRuntimes[version.stringValue] isEqualToString:name] ||
229
(isInternal && [self.selectedRuntimes[version.stringValue] isEqualToString:@"internal"])) {
230
cell.accessoryType = UITableViewCellAccessoryCheckmark;
231
} else {
232
cell.accessoryType = UITableViewCellAccessoryNone;
233
}
234
235
// Display size
236
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
237
unsigned long long folderSize = 0;
238
NSString *directory = [NSString stringWithFormat:@"%@/java_runtimes/%@",
239
isInternal ? NSBundle.mainBundle.bundlePath : @(getenv("POJAV_HOME")),
240
name];
241
[NSFileManager.defaultManager nr_getAllocatedSize:&folderSize ofDirectoryAtURL:[NSURL fileURLWithPath:directory] error:nil];
242
dispatch_async(dispatch_get_main_queue(), ^{
243
cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ - %@",
244
[self javaVersionForPath:directory],
245
[NSByteCountFormatter stringFromByteCount:folderSize countStyle:NSByteCountFormatterCountStyleMemory]];
246
});
247
});
248
249
return cell;
250
}
251
252
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
253
[tableView deselectRowAtIndexPath:indexPath animated:NO];
254
if (indexPath.section == 0) {
255
[self tableView:tableView openPickerAtIndexPath:indexPath
256
minVersion:(indexPath.row==1 ? 17 : 8)];
257
return;
258
} else if (self.sortedJavaVersions[indexPath.section].intValue == INVALID_JRE) {
259
// TODO: do something, like alert explaining that the runtime has missing files
260
return;
261
}
262
263
// Update preference
264
NSNumber *version = self.sortedJavaVersions[indexPath.section];
265
NSString *name = self.javaRuntimes[version][indexPath.row];
266
BOOL isInternal = [objc_getAssociatedObject(name, @"internalJRE") boolValue];
267
if (isInternal) {
268
self.selectedRuntimes[version.stringValue] = @"internal";
269
} else {
270
self.selectedRuntimes[version.stringValue] = name;
271
}
272
setPrefObject(@"java.java_homes", self.selectedRuntimes);
273
274
// Update checkmark
275
NSInteger runtimeCount = [self tableView:tableView numberOfRowsInSection:indexPath.section];
276
for (int i = 0; i < runtimeCount; i++) {
277
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:indexPath.section]];
278
if (i == indexPath.row) {
279
cell.accessoryType = UITableViewCellAccessoryCheckmark;
280
} else {
281
cell.accessoryType = UITableViewCellAccessoryNone;
282
}
283
}
284
}
285
286
#pragma mark Context Menu configuration
287
288
- (void)tableView:(UITableView *)tableView openPickerAtIndexPath:(NSIndexPath *)indexPath minVersion:(NSInteger)minVer {
289
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
290
//NSDictionary *item = self.prefContents[indexPath.section][indexPath.row];
291
292
NSMutableArray *menuItems = [NSMutableArray new];
293
for (int i = 1; i < self.sortedJavaVersions.count; i++) {
294
if (self.sortedJavaVersions[i].intValue < minVer ||
295
self.sortedJavaVersions[i].intValue == INVALID_JRE) {
296
continue;
297
}
298
NSString *version = [self tableView:tableView titleForHeaderInSection:i];
299
[menuItems addObject:[UIAction
300
actionWithTitle:version
301
image:nil
302
identifier:nil
303
handler:^(UIAction *action) {
304
cell.detailTextLabel.text = version;
305
((NSMutableDictionary *)self.selectedRuntimes[@"0"])[self.selectedRTTags[indexPath.row]] = self.sortedJavaVersions[i].stringValue;
306
setPrefObject(@"java.java_homes", self.selectedRuntimes);
307
}]];
308
}
309
310
cell.detailTextLabel.interactions = [NSArray new];
311
312
if (menuItems.count == 0) {
313
[menuItems addObject:[UIAction
314
actionWithTitle:localize(@"None", nil)
315
image:nil
316
identifier:nil
317
handler:^(UIAction *action){}]];
318
}
319
320
self.currentMenu = [UIMenu menuWithTitle:cell.textLabel.text children:menuItems];
321
UIContextMenuInteraction *interaction = [[UIContextMenuInteraction alloc] initWithDelegate:self];
322
[cell.detailTextLabel addInteraction:interaction];
323
[interaction _presentMenuAtLocation:CGPointZero];
324
}
325
326
- (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction configurationForMenuAtLocation:(CGPoint)location
327
{
328
return [UIContextMenuConfiguration configurationWithIdentifier:nil previewProvider:nil actionProvider:^UIMenu * _Nullable(NSArray<UIMenuElement *> * _Nonnull suggestedActions) {
329
return self.currentMenu;
330
}];
331
}
332
333
- (_UIContextMenuStyle *)_contextMenuInteraction:(UIContextMenuInteraction *)interaction
334
styleForMenuWithConfiguration:(UIContextMenuConfiguration *)configuration
335
{
336
_UIContextMenuStyle *style = [_UIContextMenuStyle defaultStyle];
337
style.preferredLayout = 3; // _UIContextMenuLayoutCompactMenu
338
return style;
339
}
340
341
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
342
{
343
if (editingStyle != UITableViewCellEditingStyleDelete) return;
344
345
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
346
NSString *title = localize(@"preference.title.confirm", nil);
347
NSString *message = [NSString stringWithFormat:localize(@"preference.title.confirm.delete_runtime", nil), cell.textLabel.text];
348
UIAlertController *confirmAlert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleActionSheet];
349
confirmAlert.popoverPresentationController.sourceView = cell;
350
confirmAlert.popoverPresentationController.sourceRect = cell.bounds;
351
UIAlertAction *ok = [UIAlertAction actionWithTitle:localize(@"OK", nil) style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
352
NSError *error;
353
NSString *directory = [NSString stringWithFormat:@"%s/java_runtimes/%@", getenv("POJAV_HOME"), cell.textLabel.text];
354
[NSFileManager.defaultManager removeItemAtPath:directory error:&error];
355
if(!error) {
356
[self removeRuntimeAtIndexPath:indexPath];
357
[self.tableView reloadData];
358
} else {
359
showDialog(localize(@"Error", nil), error.localizedDescription);
360
}
361
}];
362
UIAlertAction *cancel = [UIAlertAction actionWithTitle:localize(@"Cancel", nil) style:UIAlertActionStyleCancel handler:nil];
363
[confirmAlert addAction:cancel];
364
[confirmAlert addAction:ok];
365
[self presentViewController:confirmAlert animated:YES completion:nil];
366
}
367
368
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
369
{
370
NSNumber *version = self.sortedJavaVersions[indexPath.section];
371
NSString *name = self.javaRuntimes[version][indexPath.row];
372
BOOL isInternal = [objc_getAssociatedObject(name, @"internalJRE") boolValue];
373
BOOL isInstalling = [objc_getAssociatedObject(name, @"installing") boolValue] && currentProgressView.fractionCompleted > 0.0f;
374
if (isInternal || isInstalling) {
375
return UITableViewCellEditingStyleNone;
376
} else {
377
return UITableViewCellEditingStyleDelete;
378
}
379
}
380
381
#pragma mark Version parser
382
383
- (NSString *)javaVersionForPath:(NSString *)path {
384
path = [path stringByAppendingPathComponent:@"release"];
385
NSError *error;
386
NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
387
if (error) {
388
return [NSString stringWithFormat:@"Error: %@", error.localizedDescription];
389
}
390
391
content = [content componentsSeparatedByString:@"JAVA_VERSION=\""][1];
392
content = [content componentsSeparatedByString:@"\""][0];
393
return content;
394
}
395
396
- (NSNumber *)majorVersionForFullVersion:(NSString *)version {
397
if ([version hasPrefix:@"1.8.0"]) {
398
return @8;
399
} else if ([version hasPrefix:@"Error: "]){
400
return @(INVALID_JRE);
401
} else {
402
return @([version componentsSeparatedByString:@"."][0].intValue);
403
}
404
}
405
- (NSNumber *)majorVersionForPath:(NSString *)path {
406
return [self majorVersionForFullVersion:[self javaVersionForPath:path]];
407
}
408
409
- (void)addRuntimePath:(NSString *)path markInternal:(BOOL)markInternal {
410
NSNumber *majorVer = [self majorVersionForPath:path];
411
if (!self.javaRuntimes[majorVer]) {
412
self.javaRuntimes[majorVer] = [NSMutableArray new];
413
[self.sortedJavaVersions addObject:majorVer];
414
[self.sortedJavaVersions sortUsingSelector:@selector(compare:)];
415
}
416
417
NSString *file = path.lastPathComponent;
418
objc_setAssociatedObject(file, @"internalJRE", @(markInternal), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
419
if (!markInternal) {
420
NSString *installingDir = [path stringByAppendingPathComponent:@".installing"];
421
BOOL markInstalling = [NSFileManager.defaultManager fileExistsAtPath:installingDir isDirectory:nil];
422
objc_setAssociatedObject(file, @"installing", @(markInstalling), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
423
}
424
425
[self.javaRuntimes[majorVer] addObject:file];
426
}
427
428
- (void)removeRuntimeAtIndexPath:(NSIndexPath *)indexPath {
429
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
430
NSNumber *version = self.sortedJavaVersions[indexPath.section];
431
NSString *name = self.javaRuntimes[version][indexPath.row];
432
433
[self.javaRuntimes[version] removeObject:name];
434
if (self.javaRuntimes[version].count == 0) {
435
[self.javaRuntimes removeObjectForKey:version];
436
[self.selectedRuntimes removeObjectForKey:version.stringValue];
437
[self.sortedJavaVersions removeObject:version];
438
} else if (cell.accessoryType == UITableViewCellAccessoryCheckmark) {
439
self.selectedRuntimes[version.stringValue] = self.javaRuntimes[version][0];
440
}
441
442
setPrefObject(@"java.java_homes", self.selectedRuntimes);
443
}
444
445
- (void)listJREInPath:(NSString *)path markInternal:(BOOL)markInternal {
446
NSFileManager *fm = NSFileManager.defaultManager;
447
NSArray *files = [fm contentsOfDirectoryAtPath:path error:nil];
448
BOOL isDir;
449
for (NSString *file in files) {
450
NSString *rtPath = [path stringByAppendingPathComponent:file];
451
[fm fileExistsAtPath:rtPath isDirectory:&isDir];
452
if (isDir) {
453
[self addRuntimePath:rtPath markInternal:markInternal];
454
}
455
}
456
}
457
458
#pragma mark Extract tar.xz
459
460
+ (NSDictionary *)parseRuntimeInfo:(NSString *)path {
461
NSError *error;
462
463
NSString *releaseFile = [path stringByAppendingPathComponent:@"release"];
464
NSString *content = [NSString stringWithContentsOfFile:releaseFile encoding:NSUTF8StringEncoding error:&error];
465
if (error) {
466
return @{@"Error": error.localizedDescription};
467
}
468
469
NSMutableDictionary *dict = [NSMutableDictionary new];
470
for (NSString *line in [content componentsSeparatedByCharactersInSet:NSCharacterSet.newlineCharacterSet]) {
471
if (line.length > 0) {
472
NSArray *keyValue = [[line substringToIndex:line.length-1] componentsSeparatedByString:@"=\""];
473
dict[keyValue[0]] = keyValue[1];
474
}
475
}
476
return dict;
477
}
478
479
+ (NSString *)validateRuntimeInfo:(NSString *)path {
480
NSDictionary *dict = [self parseRuntimeInfo:path];
481
482
if (dict[@"Error"]) {
483
return dict[@"Error"];
484
} else if (![dict[@"OS_ARCH"] isEqualToString:@"aarch64"]) {
485
return [NSString stringWithFormat:@"Wrong runtime architecture: %@ (need aarch64)", dict[@"OS_ARCH"]];
486
} else if (![dict[@"OS_NAME"] isEqualToString:@"Darwin"]) {
487
return [NSString stringWithFormat:@"Wrong runtime platform: %@ (need Darwin)", dict[@"OS_NAME"]];
488
}
489
490
dispatch_async(dispatch_get_main_queue(), ^{
491
[self.currentInstance addRuntimePath:path markInternal:NO];
492
[self.currentInstance.tableView reloadData];
493
});
494
495
dispatch_async(dispatch_get_main_queue(), ^{
496
[self.currentInstance.tableView scrollToRowAtIndexPath:self.currentInstance.installingIndexPath
497
atScrollPosition:UITableViewScrollPositionBottom animated:YES];
498
});
499
return nil;
500
}
501
502
+ (NSString *)lzmaErrorDescriptionForCode:(int)code {
503
switch (code) {
504
case LZMA_MEM_ERROR:
505
return @"Memory allocation failed";
506
case LZMA_FORMAT_ERROR:
507
return @"The input is not in the .xz format";
508
case LZMA_OPTIONS_ERROR:
509
return @"Unsupported compression options";
510
case LZMA_DATA_ERROR:
511
return @"Compressed file is corrupt";
512
case LZMA_BUF_ERROR:
513
return @"Compressed file is truncated or otherwise corrupt";
514
default:
515
return @"Unknown error, possibly a bug";
516
}
517
}
518
519
// Reference: https://github.com/xz-mirror/xz/blob/master/doc/examples/02_decompress.c
520
+ (NSString *)extractTarXZ:(NSString *)inPath to:(NSString *)outPath progress:(NSProgress *)progress fileProgress:(NSProgress *)fileProgress fileCallback:(void(^)(NSString* name))fileCallback {
521
NSString *installingDir = [outPath stringByAppendingPathComponent:@".installing"];
522
[NSFileManager.defaultManager createDirectoryAtPath:installingDir withIntermediateDirectories:YES attributes:nil error:nil];
523
524
NSInputStream *inFile = [NSInputStream inputStreamWithFileAtPath:inPath];
525
[inFile open];
526
527
NSString *msg = nil;
528
lzma_stream strm = LZMA_STREAM_INIT;
529
lzma_action action = LZMA_RUN;
530
uint8_t inbuf[BUFSIZ], outbuf[512];
531
532
lzma_ret ret = lzma_stream_decoder(&strm, UINT64_MAX, LZMA_CONCATENATED);
533
if (ret != LZMA_OK) {
534
return [self lzmaErrorDescriptionForCode:ret];
535
}
536
537
strm.next_in = NULL;
538
strm.avail_in = 0;
539
strm.next_out = outbuf;
540
strm.avail_out = sizeof(outbuf);
541
542
TarHeader currFileHeader;
543
NSString *currFileName;
544
NSOutputStream *currFileOut;
545
NSUInteger currFileOff, currFileSize;
546
547
while (!progress.cancelled) {
548
if (strm.avail_in == 0 && inFile.hasBytesAvailable) {
549
strm.next_in = inbuf;
550
strm.avail_in = [inFile read:inbuf maxLength:sizeof(inbuf)];
551
552
if (strm.avail_in == -1) {
553
msg = inFile.streamError.localizedDescription;
554
break;
555
}
556
557
if (!inFile.hasBytesAvailable) {
558
action = LZMA_FINISH;
559
}
560
}
561
562
ret = lzma_code(&strm, action);
563
if (strm.avail_out == 0 || ret == LZMA_STREAM_END) {
564
if (currFileOut) {
565
size_t remaining = currFileSize - currFileOff;
566
size_t write_size = MIN(sizeof(outbuf), remaining);
567
currFileOff += write_size;
568
// Avoid overloading the main queue
569
if (currFileOff % 102400 == 0) {
570
dispatch_async(dispatch_get_main_queue(), ^{
571
fileProgress.completedUnitCount = currFileOff;
572
fileCallback(currFileName);
573
});
574
}
575
if ([currFileOut write:outbuf maxLength:write_size] != write_size) {
576
msg = [NSString stringWithFormat:@"%s: %@", currFileHeader.name, currFileOut.streamError];
577
break;
578
} else if (currFileOff >= currFileSize) {
579
[currFileOut close];
580
currFileOut = nil;
581
if ([currFileName isEqualToString:@"./release"]) {
582
msg = [LauncherPrefManageJREViewController validateRuntimeInfo:outPath];
583
if (msg) goto cleanup;
584
}
585
}
586
} else {
587
memcpy(&currFileHeader, outbuf, sizeof(outbuf));
588
if (currFileHeader.name[0] == '\0') {
589
// EOF
590
break;
591
}
592
NSString *absPath = [NSString stringWithFormat:@"%@/%s", outPath, currFileHeader.name];
593
NSError *error = nil;
594
switch (currFileHeader.typeflag) {
595
case '0':
596
case '\0': { // File
597
currFileName = @(currFileHeader.name);
598
currFileOff = fileProgress.completedUnitCount = 0;
599
currFileSize = fileProgress.totalUnitCount = strtol(currFileHeader.size, NULL, 8);
600
NSLog(@"[RuntimeUnpack] Extracting %@", currFileName);
601
dispatch_async(dispatch_get_main_queue(), ^{
602
fileCallback(currFileName);
603
});
604
605
currFileOut = [NSOutputStream outputStreamToFileAtPath:absPath append:NO];
606
[currFileOut open];
607
} break;
608
case '2': { // Symlink
609
symlink(currFileHeader.linkname, currFileHeader.name);
610
//NSLog(@"%s -> %s", currFileHeader.name, currFileHeader.linkname);
611
} break;
612
case '5': { // Folder
613
[NSFileManager.defaultManager createDirectoryAtPath:absPath withIntermediateDirectories:YES attributes:nil error:&error];
614
if (error) {
615
msg = [NSString stringWithFormat:@"%s: %@", currFileHeader.name, error];
616
goto cleanup;
617
}
618
} break;
619
default: // Ignore everything else
620
if (currFileHeader.typeflag < '0' && currFileHeader.typeflag > '7'
621
&& (currFileHeader.typeflag != 'x' && currFileHeader.typeflag != 'g')) {
622
msg = [NSString stringWithFormat:@"Invalid typeflag %c", currFileHeader.typeflag];
623
goto cleanup;
624
}
625
NSLog(@"[RuntimeUnpack] Skipped %s (typeflag %c)", currFileHeader.name, currFileHeader.typeflag);
626
break;
627
}
628
}
629
630
strm.next_out = outbuf;
631
strm.avail_out = sizeof(outbuf);
632
633
progress.completedUnitCount = strm.total_in;
634
}
635
636
if (ret == LZMA_STREAM_END) {
637
break;
638
} else if (ret != LZMA_OK) {
639
msg = [self lzmaErrorDescriptionForCode:ret];
640
break;
641
}
642
}
643
644
cleanup:
645
if (msg || progress.cancelled) {
646
[NSFileManager.defaultManager removeItemAtPath:outPath error:nil];
647
} else {
648
[NSFileManager.defaultManager removeItemAtPath:installingDir error:nil];
649
}
650
lzma_end(&strm);
651
[inFile close];
652
[currFileOut close];
653
return msg;
654
}
655
656
@end
657
658