@@ -24,6 +24,12 @@ const LC_LOAD_DYLIB: u32 = 0xc;
2424const LC_LOAD_WEAK_DYLIB : u32 = 0x80000018 ;
2525const LC_SEGMENT_64 : u32 = 0x19 ;
2626const LC_CODE_SIGNATURE : u32 = 0x1d ;
27+ const LC_VERSION_MIN_MACOSX : u32 = 0x24 ;
28+ const LC_VERSION_MIN_IPHONEOS : u32 = 0x25 ;
29+ const LC_BUILD_VERSION : u32 = 0x32 ;
30+
31+ const PLATFORM_MACOS : u32 = 1 ;
32+ const PLATFORM_IOS : u32 = 2 ;
2733
2834const BUFSIZE : usize = 512 ;
2935
@@ -128,6 +134,26 @@ struct LinkeditDataCommand {
128134 datasize : u32 ,
129135}
130136
137+ #[ repr( C ) ]
138+ #[ derive( Clone , Copy , Debug ) ]
139+ struct VersionMinCommand {
140+ cmd : u32 ,
141+ cmdsize : u32 ,
142+ version : u32 ,
143+ sdk : u32 ,
144+ }
145+
146+ #[ repr( C ) ]
147+ #[ derive( Clone , Copy , Debug ) ]
148+ struct BuildVersionCommand {
149+ cmd : u32 ,
150+ cmdsize : u32 ,
151+ platform : u32 ,
152+ minos : u32 ,
153+ sdk : u32 ,
154+ ntools : u32 ,
155+ }
156+
131157#[ repr( C ) ]
132158#[ derive( Clone , Copy , Debug ) ]
133159struct FatHeader {
@@ -152,6 +178,14 @@ struct Options {
152178 overwrite : bool ,
153179 codesig_flag : u8 ,
154180 all_yes : bool ,
181+ ios : bool ,
182+ ios_dylib_path : Option < String > ,
183+ }
184+
185+ #[ derive( Clone , Copy , Debug , Default ) ]
186+ struct PlatformRewriteStats {
187+ platform_commands : usize ,
188+ rewritten_commands : usize ,
155189}
156190
157191fn is_64_bit ( magic : u32 ) -> bool {
@@ -193,8 +227,9 @@ fn absdiff(lhs: u64, rhs: u64) -> u64 {
193227fn usage ( ) -> ! {
194228 println ! ( "Usage: insert-dylib dylib_path binary_path [new_binary_path]" ) ;
195229 println ! (
196- "Option flags: --inplace --weak --overwrite --strip-codesig --no-strip-codesig --all-yes"
230+ "Option flags: --inplace --weak --overwrite --strip-codesig --no-strip-codesig --all-yes --ios --dylib-path <path> "
197231 ) ;
232+ println ! ( "Note: --ios requires --dylib-path <local_dylib_file>." ) ;
198233 process:: exit ( 1 ) ;
199234}
200235
@@ -599,12 +634,133 @@ fn insert_dylib(
599634 Ok ( true )
600635}
601636
637+ fn rewrite_macho_platform_to_ios_slice (
638+ file : & mut File ,
639+ header_offset : u64 ,
640+ ) -> io:: Result < PlatformRewriteStats > {
641+ file. seek ( SeekFrom :: Start ( header_offset) ) ?;
642+
643+ let mh: MachHeader = read_struct ( file) ?;
644+ if mh. magic != MH_MAGIC_64
645+ && mh. magic != MH_CIGAM_64
646+ && mh. magic != MH_MAGIC
647+ && mh. magic != MH_CIGAM
648+ {
649+ return Err ( io:: Error :: new (
650+ io:: ErrorKind :: InvalidData ,
651+ format ! ( "Unknown Mach-O magic: 0x{:x}" , mh. magic) ,
652+ ) ) ;
653+ }
654+
655+ let commands_offset = header_offset
656+ + if is_64_bit ( mh. magic ) {
657+ size_of :: < MachHeader64 > ( ) as u64
658+ } else {
659+ size_of :: < MachHeader > ( ) as u64
660+ } ;
661+
662+ file. seek ( SeekFrom :: Start ( commands_offset) ) ?;
663+ let ncmds = swap32 ( mh. ncmds , mh. magic ) ;
664+ let mut stats = PlatformRewriteStats :: default ( ) ;
665+
666+ for _ in 0 ..ncmds {
667+ let command_offset = file. stream_position ( ) ?;
668+ let lc: LoadCommand = peek_struct ( file) ?;
669+ let cmd = swap32 ( lc. cmd , mh. magic ) ;
670+ let cmdsize = swap32 ( lc. cmdsize , mh. magic ) ;
671+
672+ if cmdsize < size_of :: < LoadCommand > ( ) as u32 {
673+ return Err ( io:: Error :: new (
674+ io:: ErrorKind :: InvalidData ,
675+ "load command size is smaller than load_command" ,
676+ ) ) ;
677+ }
678+
679+ match cmd {
680+ LC_BUILD_VERSION => {
681+ if cmdsize < size_of :: < BuildVersionCommand > ( ) as u32 {
682+ return Err ( io:: Error :: new (
683+ io:: ErrorKind :: InvalidData ,
684+ "LC_BUILD_VERSION command is truncated" ,
685+ ) ) ;
686+ }
687+
688+ let mut cmd_data: BuildVersionCommand = read_struct ( file) ?;
689+ let platform = swap32 ( cmd_data. platform , mh. magic ) ;
690+ stats. platform_commands += 1 ;
691+
692+ if platform == PLATFORM_MACOS {
693+ cmd_data. platform = swap32 ( PLATFORM_IOS , mh. magic ) ;
694+ file. seek ( SeekFrom :: Start ( command_offset) ) ?;
695+ write_struct ( file, & cmd_data) ?;
696+ stats. rewritten_commands += 1 ;
697+ }
698+ }
699+ LC_VERSION_MIN_MACOSX => {
700+ if cmdsize < size_of :: < VersionMinCommand > ( ) as u32 {
701+ return Err ( io:: Error :: new (
702+ io:: ErrorKind :: InvalidData ,
703+ "LC_VERSION_MIN_MACOSX command is truncated" ,
704+ ) ) ;
705+ }
706+
707+ let mut cmd_data: VersionMinCommand = read_struct ( file) ?;
708+ cmd_data. cmd = swap32 ( LC_VERSION_MIN_IPHONEOS , mh. magic ) ;
709+ file. seek ( SeekFrom :: Start ( command_offset) ) ?;
710+ write_struct ( file, & cmd_data) ?;
711+ stats. platform_commands += 1 ;
712+ stats. rewritten_commands += 1 ;
713+ }
714+ LC_VERSION_MIN_IPHONEOS => {
715+ stats. platform_commands += 1 ;
716+ }
717+ _ => { }
718+ }
719+
720+ file. seek ( SeekFrom :: Start ( command_offset + cmdsize as u64 ) ) ?;
721+ }
722+
723+ Ok ( stats)
724+ }
725+
726+ fn rewrite_dylib_platform_to_ios ( path : & str ) -> io:: Result < PlatformRewriteStats > {
727+ let mut file = File :: options ( ) . read ( true ) . write ( true ) . open ( path) ?;
728+ let magic: u32 = read_struct ( & mut file) ?;
729+
730+ match magic {
731+ FAT_MAGIC | FAT_CIGAM => {
732+ file. seek ( SeekFrom :: Start ( 0 ) ) ?;
733+ let fat_header: FatHeader = read_struct ( & mut file) ?;
734+ let nfat_arch = swap32 ( fat_header. nfat_arch , magic) as usize ;
735+ let archs = read_fat_arches ( & mut file, nfat_arch) ?;
736+
737+ let mut total = PlatformRewriteStats :: default ( ) ;
738+ for arch in archs {
739+ let offset = swap32 ( arch. offset , magic) as u64 ;
740+ let stats = rewrite_macho_platform_to_ios_slice ( & mut file, offset) ?;
741+ total. platform_commands += stats. platform_commands ;
742+ total. rewritten_commands += stats. rewritten_commands ;
743+ }
744+ Ok ( total)
745+ }
746+ MH_MAGIC_64 | MH_CIGAM_64 | MH_MAGIC | MH_CIGAM => {
747+ file. seek ( SeekFrom :: Start ( 0 ) ) ?;
748+ rewrite_macho_platform_to_ios_slice ( & mut file, 0 )
749+ }
750+ _ => Err ( io:: Error :: new (
751+ io:: ErrorKind :: InvalidData ,
752+ format ! ( "Unknown Mach-O magic: 0x{:x}" , magic) ,
753+ ) ) ,
754+ }
755+ }
756+
602757fn parse_args ( ) -> ( Options , Vec < String > ) {
603758 let mut options = Options :: default ( ) ;
604759 let mut positional = Vec :: new ( ) ;
605760
761+ let mut args = env:: args ( ) . skip ( 1 ) . peekable ( ) ;
606762 let mut parsing_options = true ;
607- for arg in env :: args ( ) . skip ( 1 ) {
763+ while let Some ( arg) = args. next ( ) {
608764 if parsing_options {
609765 if arg == "--" {
610766 parsing_options = false ;
@@ -619,7 +775,21 @@ fn parse_args() -> (Options, Vec<String>) {
619775 "--strip-codesig" => options. codesig_flag = 1 ,
620776 "--no-strip-codesig" => options. codesig_flag = 2 ,
621777 "--all-yes" => options. all_yes = true ,
622- _ => usage ( ) ,
778+ "--ios" => options. ios = true ,
779+ "--dylib-path" => {
780+ let path = args. next ( ) . unwrap_or_else ( || usage ( ) ) ;
781+ options. ios_dylib_path = Some ( path) ;
782+ }
783+ _ => {
784+ if let Some ( path) = arg. strip_prefix ( "--dylib-path=" ) {
785+ if path. is_empty ( ) {
786+ usage ( ) ;
787+ }
788+ options. ios_dylib_path = Some ( path. to_string ( ) ) ;
789+ } else {
790+ usage ( ) ;
791+ }
792+ }
623793 }
624794 continue ;
625795 }
@@ -677,6 +847,35 @@ fn run() -> io::Result<i32> {
677847 return Ok ( 1 ) ;
678848 }
679849
850+ if options. ios_dylib_path . is_some ( ) && !options. ios {
851+ eprintln ! ( "--dylib-path can only be used together with --ios." ) ;
852+ return Ok ( 1 ) ;
853+ }
854+
855+ if options. ios {
856+ let ios_dylib_path = options. ios_dylib_path . as_deref ( ) . unwrap_or_else ( || {
857+ eprintln ! ( "--ios requires --dylib-path <local_dylib_file>." ) ;
858+ usage ( ) ;
859+ } ) ;
860+
861+ if fs:: metadata ( ios_dylib_path) . is_err ( ) {
862+ eprintln ! ( "{ios_dylib_path}: not found (--dylib-path)" ) ;
863+ return Ok ( 1 ) ;
864+ }
865+
866+ let stats = rewrite_dylib_platform_to_ios ( ios_dylib_path) ?;
867+ if stats. platform_commands == 0 {
868+ println ! ( "No platform load command found in {ios_dylib_path}; skipped --ios rewrite." ) ;
869+ } else if stats. rewritten_commands == 0 {
870+ println ! ( "{ios_dylib_path} already declares iOS platform; no rewrite needed." ) ;
871+ } else {
872+ println ! (
873+ "Rewrote {} platform load command(s) from macOS to iOS in {ios_dylib_path}." ,
874+ stats. rewritten_commands
875+ ) ;
876+ }
877+ }
878+
680879 if !options. inplace {
681880 let new_binary_path = if positional. len ( ) == 3 {
682881 positional[ 2 ] . clone ( )
0 commit comments