24
24
import time
25
25
import unicodedata
26
26
import urls
27
+ import xattr
27
28
28
29
CHUNK_SIZE = 256 * 1024
29
30
30
31
ImportFolders = typing .Tuple [
31
32
typing .Dict [str , machfs .Folder ], # Universal folders
32
33
typing .Dict [str , machfs .Folder ], # Folders that need System 7
34
+ typing .Dict [str , machfs .Folder ], # Folders that need Mac OS X
33
35
]
34
36
35
37
def get_import_folders () -> ImportFolders :
36
38
import_folders = {}
37
39
import_folders7 = {}
40
+ import_foldersX = {}
38
41
39
- manifest_folders , manifest_folders7 = import_manifests ()
42
+ manifest_folders , manifest_folders7 , manifest_foldersX = import_manifests ()
40
43
import_folders .update (manifest_folders )
41
44
import_folders7 .update (manifest_folders7 )
45
+ import_foldersX .update (manifest_foldersX )
42
46
43
47
zip_folders , zip_folder7 = import_zips ()
44
48
import_folders .update (zip_folders )
45
49
import_folders7 .update (zip_folder7 )
46
50
47
- return import_folders , import_folders7
51
+ return import_folders , import_folders7 , import_foldersX
48
52
49
53
50
54
def import_manifests () -> ImportFolders :
51
55
sys .stderr .write ("Importing other images\n " )
52
56
import_folders = {}
53
57
import_folders7 = {}
58
+ import_foldersX = {}
54
59
debug_filter = os .getenv ("DEBUG_LIBRARY_FILTER" )
55
60
56
61
for manifest_path in glob .iglob (os .path .join (paths .LIBRARY_DIR , "**" ,
@@ -76,15 +81,22 @@ def import_manifests() -> ImportFolders:
76
81
"(build it with npm run build-xadmaster)\n " )
77
82
continue
78
83
folder = import_archive (manifest_json )
84
+ elif src_ext in [".dmg" ]:
85
+ if not os .path .exists (paths .HDIUTIL_PATH ):
86
+ sys .stderr .write (" Skipping .dmg import, hdiutil not found\n " )
87
+ continue
88
+ folder = import_dmg (manifest_json )
79
89
else :
80
90
assert False , "Unexpected manifest URL extension: %s" % src_ext
81
91
82
- if manifest_json .get ("needs_system_7" ):
92
+ if manifest_json .get ("needs_mac_os_x" ):
93
+ import_foldersX [folder_path ] = folder
94
+ elif manifest_json .get ("needs_system_7" ):
83
95
import_folders7 [folder_path ] = folder
84
96
else :
85
97
import_folders [folder_path ] = folder
86
98
87
- return import_folders , import_folders7
99
+ return import_folders , import_folders7 , import_foldersX
88
100
89
101
90
102
def import_disk_image (
@@ -319,6 +331,96 @@ def convert_date(date_str: str) -> int:
319
331
file_or_folder .crdate = convert_date (entry ["XADCreationDate" ])
320
332
321
333
334
+ def import_dmg (
335
+ manifest_json : typing .Dict [str , typing .Any ]) -> machfs .Folder :
336
+ src_url = manifest_json ["src_url" ]
337
+ archive_path = urls .read_url_to_path (src_url )
338
+ root_folder = machfs .Folder ()
339
+
340
+ def normalize (name : str ) -> str :
341
+ # Normalizes accented characters to their combined form, since only
342
+ # those have an equivalent in the MacRoman encoding that HFS ends up
343
+ # using.
344
+ return unicodedata .normalize ("NFC" , name )
345
+
346
+ with tempfile .TemporaryDirectory () as tmp_dir_path :
347
+ hdiutil_code = subprocess .call ([
348
+ paths .HDIUTIL_PATH , "attach" , archive_path , "-mountpoint" ,
349
+ tmp_dir_path
350
+ ],
351
+ stdout = subprocess .DEVNULL )
352
+ if hdiutil_code != 0 :
353
+ assert False , "Could not mount .dmg: %s (cached at %s):" % (
354
+ src_url , archive_path )
355
+ try :
356
+ # TODO: allow selection of a child folder
357
+ root_dir_path = tmp_dir_path
358
+
359
+ if "src_folder" in manifest_json :
360
+ src_folder_name = manifest_json ["src_folder" ]
361
+ root_dir_path = os .path .join (root_dir_path , src_folder_name )
362
+ update_folder_from_xattr (root_folder , root_dir_path )
363
+ clear_folder_window_position (root_folder )
364
+
365
+ for dir_path , dir_names , file_names in os .walk (root_dir_path ):
366
+ folder = root_folder
367
+ dir_rel_path = os .path .relpath (dir_path , root_dir_path )
368
+ if dir_rel_path != "." :
369
+ folder_path_pieces = []
370
+ for folder_name in dir_rel_path .split (os .path .sep ):
371
+ folder_path_pieces .append (folder_name )
372
+ folder_name = normalize (folder_name )
373
+ if folder_name not in folder :
374
+ new_folder = folder [folder_name ] = machfs .Folder ()
375
+ update_folder_from_xattr (new_folder , os .path .join (root_dir_path ,
376
+ * folder_path_pieces ))
377
+ folder = folder [folder_name ]
378
+ for file_name in file_names :
379
+ file_path = os .path .join (dir_path , file_name )
380
+ file = machfs .File ()
381
+ with open (file_path , "rb" ) as f :
382
+ file .data = f .read ()
383
+ resource_fork_path = os .path .join (file_path , "..namedfork" ,
384
+ "rsrc" )
385
+ if os .path .exists (resource_fork_path ):
386
+ with open (resource_fork_path , "rb" ) as f :
387
+ file .rsrc = f .read ()
388
+
389
+ update_file_from_xattr (file , file_path )
390
+
391
+ folder [normalize (file_name )] = file
392
+ finally :
393
+ hdiutil_code = subprocess .call ([
394
+ paths .HDIUTIL_PATH , "detach" , tmp_dir_path ])
395
+ if hdiutil_code != 0 :
396
+ assert False , "Could not unmount .dmg: %s (cached at %s):" % (
397
+ src_url , archive_path )
398
+
399
+ return root_folder
400
+
401
+
402
+ def update_file_from_xattr (file : machfs .File , file_path : str ) -> None :
403
+ attr_name = "com.apple.FinderInfo"
404
+ xattrs = xattr .listxattr (file_path )
405
+ if attr_name not in xattrs :
406
+ return
407
+ finder_info = xattr .getxattr (file_path , attr_name )
408
+ (file .type , file .creator , file .flags , file .y , file .x , _ , file .fndrInfo ) = struct .unpack (
409
+ '>4s4sHhhH16s' , finder_info )
410
+ if file .x == 0 and file .y == 0 :
411
+ file .flags &= ~ machfs .main .FinderFlags .kHasBeenInited
412
+
413
+
414
+ def update_folder_from_xattr (folder : machfs .Folder , folder_path : str ) -> None :
415
+ attr_name = "com.apple.FinderInfo"
416
+ xattrs = xattr .listxattr (folder_path )
417
+ if attr_name not in xattrs :
418
+ return
419
+ # Get creator and type code
420
+ finder_info = xattr .getxattr (folder_path , attr_name )
421
+ folder .usrInfo = finder_info [0 :16 ]
422
+ folder .fndrInfo = finder_info [16 :]
423
+
322
424
SYSTEM7_ZIP_PATHS = {
323
425
"Games/Bungie/Marathon Infinity" ,
324
426
"Graphics/Adobe Photoshop 3.0" ,
@@ -555,8 +657,8 @@ def build_system_image(
555
657
return write_image_def (image_data , disk .name , dest_dir )
556
658
557
659
558
- def build_library_images (dest_dir : str ) -> typing .Tuple [ImageDef , ImageDef ]:
559
- import_folders , import_folders7 = get_import_folders ()
660
+ def build_library_images (dest_dir : str ) -> typing .Tuple [ImageDef , ImageDef , ImageDef ]:
661
+ import_folders , import_folders7 , import_foldersX = get_import_folders ()
560
662
561
663
v = machfs .Volume ()
562
664
with open (os .path .join (paths .IMAGES_DIR , "Infinite HD.dsk" ), "rb" ) as base :
@@ -593,7 +695,22 @@ def add_folders(folders: typing.Dict[str, machfs.Folder]) -> None:
593
695
)
594
696
image_def = write_image_def (image , "Infinite HD.dsk" , dest_dir )
595
697
596
- return image6_def , image_def
698
+ # Not much point in including Classic software for the Mac OS X image, so
699
+ # we start with a fresh volume.
700
+ v = machfs .Volume ()
701
+ with open (os .path .join (paths .IMAGES_DIR , "Infinite HD.dsk" ), "rb" ) as base :
702
+ v .read (base .read ())
703
+ v .name = "Infinite HD"
704
+ add_folders (import_foldersX )
705
+ imageX = v .write (
706
+ size = 2000 * 1024 * 1024 ,
707
+ align = 512 ,
708
+ desktopdb = False ,
709
+ bootable = False ,
710
+ )
711
+ imageX_def = write_image_def (imageX , "Infinite HDX.dsk" , dest_dir )
712
+
713
+ return image6_def , image_def , imageX_def
597
714
598
715
599
716
def build_passthrough_image (base_name : str , dest_dir : str , compressed : bool = False ) -> ImageDef :
@@ -738,12 +855,13 @@ def read_strings(name: str) -> str:
738
855
continue
739
856
images .append (build_system_image (disk , temp_dir ))
740
857
if not system_filter :
741
- infinite_hd6_image , infinite_hd_image = build_library_images (temp_dir )
858
+ infinite_hd6_image , infinite_hd_image , infinite_hdX_image = build_library_images (temp_dir )
742
859
images .append (infinite_hd6_image )
743
860
images .append (infinite_hd_image )
861
+ images .append (infinite_hdX_image )
744
862
if not library_filter :
745
863
build_desktop_db6 ([infinite_hd6_image ])
746
- build_desktop_db ([infinite_hd_image ])
864
+ build_desktop_db ([infinite_hd_image , infinite_hdX_image ])
747
865
748
866
images .append (
749
867
build_passthrough_image ("Infinite HD (MFS).dsk" ,
0 commit comments