This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision Next revision Both sides next revision | ||
howto:fridahooklibrary [2017/10/23 03:32] czokie [setSSLPinningMode] |
howto:fridahooklibrary [2017/11/06 22:59] czokie [PrettyWoman] |
||
---|---|---|---|
Line 28: | Line 28: | ||
We'll dig into this more later when/if we need to access parameter data etc. | We'll dig into this more later when/if we need to access parameter data etc. | ||
+ | ====== PrettyWoman ====== | ||
+ | As part of this work, I looked at how to " | ||
+ | |||
+ | Anyway. Naming conventions aside. How does it work? There is a shell script that parses a CSV file that contains configuration information for a list of hooks that will be created in the output. | ||
+ | |||
+ | ===== PrettyWoman.sh ===== | ||
+ | <code shell PrettyWoman.sh> | ||
+ | #!/bin/bash | ||
+ | |||
+ | function iosheader { | ||
+ | cat <<EOF | ||
+ | // | ||
+ | // DJI GO 4 - Frida Tweaks | ||
+ | // | ||
+ | |||
+ | if (ObjC.available) { | ||
+ | |||
+ | EOF | ||
+ | } | ||
+ | |||
+ | function iosfooter { | ||
+ | cat <<EOF | ||
+ | } | ||
+ | EOF | ||
+ | } | ||
+ | |||
+ | function functionheader { | ||
+ | cat <<EOF | ||
+ | |||
+ | // | ||
+ | // ${1} | ||
+ | // | ||
+ | |||
+ | EOF | ||
+ | } | ||
+ | function jsobject { | ||
+ | |||
+ | cat <<EOF | ||
+ | var ${1} = ObjC.classes.${1}; | ||
+ | var ${3} = ${1}[' | ||
+ | var ${3}Impl = ${3}.implementation; | ||
+ | ${3}.implementation = ObjC.implement(${3}, | ||
+ | ${3}Impl(handle, | ||
+ | if ( originalResult != ${4} ) { | ||
+ | if ( ${7} == 0 || ${CLASS}${METHOD}SeenReplace == 0 ) { | ||
+ | console.log(" | ||
+ | ${CLASS}${METHOD}SeenReplace = 1; | ||
+ | } | ||
+ | } else { | ||
+ | if ( ${7} == 0 || ${CLASS}${METHOD}SeenHit == 0 ) { | ||
+ | console.log(" | ||
+ | ${CLASS}${METHOD}SeenHit = 1; | ||
+ | } | ||
+ | } | ||
+ | }); | ||
+ | EOF | ||
+ | } | ||
+ | |||
+ | function jsfunction { | ||
+ | cat <<EOF | ||
+ | var ${1} = ObjC.classes.${1}; | ||
+ | var ${3} = ${1}[' | ||
+ | var ${3}Impl = ${3}.implementation; | ||
+ | ${3}.implementation = ObjC.implement(${3}, | ||
+ | var originalResult = ${3}Impl(handle, | ||
+ | if ( originalResult != ${4} ) { | ||
+ | if ( ${7} == 0 || ${CLASS}${METHOD}SeenReplace == 0 ) { | ||
+ | console.log(" | ||
+ | ${CLASS}${METHOD}SeenReplace = 1; | ||
+ | } | ||
+ | } else { | ||
+ | if ( ${7} == 0 || ${CLASS}${METHOD}SeenHit == 0 ) { | ||
+ | console.log(" | ||
+ | ${CLASS}${METHOD}SeenHit = 1; | ||
+ | } | ||
+ | } | ||
+ | return ${4}; | ||
+ | }); | ||
+ | EOF | ||
+ | } | ||
+ | |||
+ | |||
+ | function jsfunctioniffcc { | ||
+ | cat <<EOF | ||
+ | var ${1} = ObjC.classes.${1}; | ||
+ | var ${3} = ${1}[' | ||
+ | var ${3}Impl = ${3}.implementation; | ||
+ | ${3}.implementation = ObjC.implement(${3}, | ||
+ | var originalResult = ${3}Impl(handle, | ||
+ | if ( originalResult != ${4} ) { | ||
+ | if ( ${7} == 0 || ${CLASS}${METHOD}SeenReplace == 0 ) { | ||
+ | console.log(" | ||
+ | ${CLASS}${METHOD}SeenReplace = 1; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | if( DJIAppSettingssdr_force_fccSeenReplace == 0 ) { | ||
+ | return ${4}; //mavic | ||
+ | } else { | ||
+ | return originalResult; | ||
+ | } | ||
+ | }); | ||
+ | EOF | ||
+ | } | ||
+ | |||
+ | |||
+ | function defines { | ||
+ | cat <<EOF | ||
+ | // | ||
+ | // Create variables that we can use for "one shot" rules | ||
+ | // | ||
+ | EOF | ||
+ | |||
+ | while read X | ||
+ | do | ||
+ | # | ||
+ | CLASS=`echo $X | cut -d "," | ||
+ | METHOD=`echo $X | cut -d "," | ||
+ | PLUSMINUS=`echo $X | cut -d "," | ||
+ | # | ||
+ | # | ||
+ | # | ||
+ | # | ||
+ | |||
+ | echo " | ||
+ | echo " | ||
+ | |||
+ | done < PrettyWoman.csv | ||
+ | } | ||
+ | |||
+ | function mainloop { | ||
+ | while read X | ||
+ | do | ||
+ | TEMPLATE=`echo $X | cut -d "," | ||
+ | CLASS=`echo $X | cut -d "," | ||
+ | METHOD=`echo $X | cut -d "," | ||
+ | PLUSMINUS=`echo $X | cut -d "," | ||
+ | REPLACE=`echo $X | cut -d "," | ||
+ | LOGHIT=`echo $X | cut -d "," | ||
+ | LOGCHANGED=`echo $X | cut -d "," | ||
+ | ONESHOT=`echo $X | cut -d "," | ||
+ | |||
+ | functionheader " | ||
+ | ${TEMPLATE} ${CLASS} ${PLUSMINUS} ${METHOD} ${REPLACE} " | ||
+ | done < PrettyWoman.csv | ||
+ | } | ||
+ | |||
+ | iosheader | ||
+ | defines | ||
+ | mainloop | ||
+ | iosfooter | ||
+ | </ | ||
+ | |||
+ | ===== PrettyWoman.csv ===== | ||
+ | Below is a CSV config file for PrettyWoman.sh. This file has the following fields: | ||
+ | |||
+ | ^Template|The name of a template, as listed in PrettyWoman.sh| | ||
+ | ^Class|A class name from the DJI Application source code| | ||
+ | ^Method|The name of a method or function within the class| | ||
+ | ^PlusMinus|A plus or a minus sign (as defined in the DJI reversed source code)| | ||
+ | ^OurValue|What should we replace the original function return value with?| | ||
+ | ^LogMessageTweaked|What to display in debug when we successfully bypass DJI functions etc| | ||
+ | ^LogSeen|What to display in debug when we are called, but we don't change anything| | ||
+ | ^OneShot|Set this to 1 if you want to see a log message for this once only| | ||
+ | |||
+ | <code text PrettyWoman.csv> | ||
+ | jsobject, | ||
+ | jsfunction, | ||
+ | jsfunction, | ||
+ | jsfunction, | ||
+ | jsfunction, | ||
+ | jsfunction, | ||
+ | jsfunction, | ||
+ | jsfunction, | ||
+ | jsfunctioniffcc, | ||
+ | </ | ||
+ | |||
+ | ===== Tweak.js ===== | ||
+ | |||
+ | This code was generated by PrettyWoman.sh - using the CSV file above. Feel free to just use this set of hooks for your tweaking pleasure. | ||
+ | |||
+ | <code java Tweak.js> | ||
+ | // | ||
+ | // DJI GO 4 - Frida Tweaks | ||
+ | // | ||
+ | |||
+ | if (ObjC.available) { | ||
+ | |||
+ | // | ||
+ | // Create variables that we can use for "one shot" rules | ||
+ | // | ||
+ | AFSecurityPolicysetSSLPinningModeSeenHit=0; | ||
+ | AFSecurityPolicysetSSLPinningModeSeenReplace=0; | ||
+ | DJIAccountManagercheckIsAdminUserSeenHit=0; | ||
+ | DJIAccountManagercheckIsAdminUserSeenReplace=0; | ||
+ | DJIAppSettingscanUseIllegalChannelsSeenHit=0; | ||
+ | DJIAppSettingscanUseIllegalChannelsSeenReplace=0; | ||
+ | DJIRadioLogiccanUseIllegalChannelsSeenHit=0; | ||
+ | DJIRadioLogiccanUseIllegalChannelsSeenReplace=0; | ||
+ | DJIAppSettingssdr_force_fccSeenHit=0; | ||
+ | DJIAppSettingssdr_force_fccSeenReplace=0; | ||
+ | DJITermsNotificationControllershouldShowTermsSeenHit=0; | ||
+ | DJITermsNotificationControllershouldShowTermsSeenReplace=0; | ||
+ | DJIAppForceUpdateManagerhasCheckedSeenHit=0; | ||
+ | DJIAppForceUpdateManagerhasCheckedSeenReplace=0; | ||
+ | DJIUpgradeNotifyViewModelnotifyHiddenSeenHit=0; | ||
+ | DJIUpgradeNotifyViewModelnotifyHiddenSeenReplace=0; | ||
+ | DJIProductManagercurrentProductCodeSeenHit=0; | ||
+ | DJIProductManagercurrentProductCodeSeenReplace=0; | ||
+ | |||
+ | // | ||
+ | // AFSecurityPolicy setSSLPinningMode jsobject | ||
+ | // | ||
+ | |||
+ | var AFSecurityPolicy = ObjC.classes.AFSecurityPolicy; | ||
+ | var setSSLPinningMode = AFSecurityPolicy[' | ||
+ | var setSSLPinningModeImpl = setSSLPinningMode.implementation; | ||
+ | setSSLPinningMode.implementation = ObjC.implement(setSSLPinningMode, | ||
+ | setSSLPinningModeImpl(handle, | ||
+ | if ( originalResult != 0 ) { | ||
+ | if ( 00 == 0 || AFSecurityPolicysetSSLPinningModeSeenReplace == 0 ) { | ||
+ | console.log(" | ||
+ | AFSecurityPolicysetSSLPinningModeSeenReplace = 1; | ||
+ | } | ||
+ | } else { | ||
+ | if ( 00 == 0 || AFSecurityPolicysetSSLPinningModeSeenHit == 0 ) { | ||
+ | console.log(" | ||
+ | AFSecurityPolicysetSSLPinningModeSeenHit = 1; | ||
+ | } | ||
+ | } | ||
+ | }); | ||
+ | |||
+ | // | ||
+ | // DJIAccountManager checkIsAdminUser jsfunction | ||
+ | // | ||
+ | |||
+ | var DJIAccountManager = ObjC.classes.DJIAccountManager; | ||
+ | var checkIsAdminUser = DJIAccountManager[' | ||
+ | var checkIsAdminUserImpl = checkIsAdminUser.implementation; | ||
+ | checkIsAdminUser.implementation = ObjC.implement(checkIsAdminUser, | ||
+ | var originalResult = checkIsAdminUserImpl(handle, | ||
+ | if ( originalResult != 1 ) { | ||
+ | if ( 00 == 0 || DJIAccountManagercheckIsAdminUserSeenReplace == 0 ) { | ||
+ | console.log(" | ||
+ | DJIAccountManagercheckIsAdminUserSeenReplace = 1; | ||
+ | } | ||
+ | } else { | ||
+ | if ( 00 == 0 || DJIAccountManagercheckIsAdminUserSeenHit == 0 ) { | ||
+ | console.log(" | ||
+ | DJIAccountManagercheckIsAdminUserSeenHit = 1; | ||
+ | } | ||
+ | } | ||
+ | return 1; | ||
+ | }); | ||
+ | |||
+ | // | ||
+ | // DJIAppSettings canUseIllegalChannels jsfunction | ||
+ | // | ||
+ | |||
+ | var DJIAppSettings = ObjC.classes.DJIAppSettings; | ||
+ | var canUseIllegalChannels = DJIAppSettings[' | ||
+ | var canUseIllegalChannelsImpl = canUseIllegalChannels.implementation; | ||
+ | canUseIllegalChannels.implementation = ObjC.implement(canUseIllegalChannels, | ||
+ | var originalResult = canUseIllegalChannelsImpl(handle, | ||
+ | if ( originalResult != 1 ) { | ||
+ | if ( 00 == 0 || DJIAppSettingscanUseIllegalChannelsSeenReplace == 0 ) { | ||
+ | console.log(" | ||
+ | DJIAppSettingscanUseIllegalChannelsSeenReplace = 1; | ||
+ | } | ||
+ | } else { | ||
+ | if ( 00 == 0 || DJIAppSettingscanUseIllegalChannelsSeenHit == 0 ) { | ||
+ | console.log(" | ||
+ | DJIAppSettingscanUseIllegalChannelsSeenHit = 1; | ||
+ | } | ||
+ | } | ||
+ | return 1; | ||
+ | }); | ||
+ | |||
+ | // | ||
+ | // DJIRadioLogic canUseIllegalChannels jsfunction | ||
+ | // | ||
+ | |||
+ | var DJIRadioLogic = ObjC.classes.DJIRadioLogic; | ||
+ | var canUseIllegalChannels = DJIRadioLogic[' | ||
+ | var canUseIllegalChannelsImpl = canUseIllegalChannels.implementation; | ||
+ | canUseIllegalChannels.implementation = ObjC.implement(canUseIllegalChannels, | ||
+ | var originalResult = canUseIllegalChannelsImpl(handle, | ||
+ | if ( originalResult != 1 ) { | ||
+ | if ( 00 == 0 || DJIRadioLogiccanUseIllegalChannelsSeenReplace == 0 ) { | ||
+ | console.log(" | ||
+ | DJIRadioLogiccanUseIllegalChannelsSeenReplace = 1; | ||
+ | } | ||
+ | } else { | ||
+ | if ( 00 == 0 || DJIRadioLogiccanUseIllegalChannelsSeenHit == 0 ) { | ||
+ | console.log(" | ||
+ | DJIRadioLogiccanUseIllegalChannelsSeenHit = 1; | ||
+ | } | ||
+ | } | ||
+ | return 1; | ||
+ | }); | ||
+ | |||
+ | // | ||
+ | // DJIAppSettings sdr_force_fcc jsfunction | ||
+ | // | ||
+ | |||
+ | var DJIAppSettings = ObjC.classes.DJIAppSettings; | ||
+ | var sdr_force_fcc = DJIAppSettings[' | ||
+ | var sdr_force_fccImpl = sdr_force_fcc.implementation; | ||
+ | sdr_force_fcc.implementation = ObjC.implement(sdr_force_fcc, | ||
+ | var originalResult = sdr_force_fccImpl(handle, | ||
+ | if ( originalResult != 1 ) { | ||
+ | if ( 00 == 0 || DJIAppSettingssdr_force_fccSeenReplace == 0 ) { | ||
+ | console.log(" | ||
+ | DJIAppSettingssdr_force_fccSeenReplace = 1; | ||
+ | } | ||
+ | } else { | ||
+ | if ( 00 == 0 || DJIAppSettingssdr_force_fccSeenHit == 0 ) { | ||
+ | console.log(" | ||
+ | DJIAppSettingssdr_force_fccSeenHit = 1; | ||
+ | } | ||
+ | } | ||
+ | return 1; | ||
+ | }); | ||
+ | |||
+ | // | ||
+ | // DJITermsNotificationController shouldShowTerms jsfunction | ||
+ | // | ||
+ | |||
+ | var DJITermsNotificationController = ObjC.classes.DJITermsNotificationController; | ||
+ | var shouldShowTerms = DJITermsNotificationController[' | ||
+ | var shouldShowTermsImpl = shouldShowTerms.implementation; | ||
+ | shouldShowTerms.implementation = ObjC.implement(shouldShowTerms, | ||
+ | var originalResult = shouldShowTermsImpl(handle, | ||
+ | if ( originalResult != 0 ) { | ||
+ | if ( 00 == 0 || DJITermsNotificationControllershouldShowTermsSeenReplace == 0 ) { | ||
+ | console.log(" | ||
+ | DJITermsNotificationControllershouldShowTermsSeenReplace = 1; | ||
+ | } | ||
+ | } else { | ||
+ | if ( 00 == 0 || DJITermsNotificationControllershouldShowTermsSeenHit == 0 ) { | ||
+ | console.log(" | ||
+ | DJITermsNotificationControllershouldShowTermsSeenHit = 1; | ||
+ | } | ||
+ | } | ||
+ | return 0; | ||
+ | }); | ||
+ | |||
+ | // | ||
+ | // DJIAppForceUpdateManager hasChecked jsfunction | ||
+ | // | ||
+ | |||
+ | var DJIAppForceUpdateManager = ObjC.classes.DJIAppForceUpdateManager; | ||
+ | var hasChecked = DJIAppForceUpdateManager[' | ||
+ | var hasCheckedImpl = hasChecked.implementation; | ||
+ | hasChecked.implementation = ObjC.implement(hasChecked, | ||
+ | var originalResult = hasCheckedImpl(handle, | ||
+ | if ( originalResult != 1 ) { | ||
+ | if ( 00 == 0 || DJIAppForceUpdateManagerhasCheckedSeenReplace == 0 ) { | ||
+ | console.log(" | ||
+ | DJIAppForceUpdateManagerhasCheckedSeenReplace = 1; | ||
+ | } | ||
+ | } else { | ||
+ | if ( 00 == 0 || DJIAppForceUpdateManagerhasCheckedSeenHit == 0 ) { | ||
+ | console.log(" | ||
+ | DJIAppForceUpdateManagerhasCheckedSeenHit = 1; | ||
+ | } | ||
+ | } | ||
+ | return 1; | ||
+ | }); | ||
+ | |||
+ | // | ||
+ | // DJIUpgradeNotifyViewModel notifyHidden jsfunction | ||
+ | // | ||
+ | |||
+ | var DJIUpgradeNotifyViewModel = ObjC.classes.DJIUpgradeNotifyViewModel; | ||
+ | var notifyHidden = DJIUpgradeNotifyViewModel[' | ||
+ | var notifyHiddenImpl = notifyHidden.implementation; | ||
+ | notifyHidden.implementation = ObjC.implement(notifyHidden, | ||
+ | var originalResult = notifyHiddenImpl(handle, | ||
+ | if ( originalResult != 1 ) { | ||
+ | if ( 00 == 0 || DJIUpgradeNotifyViewModelnotifyHiddenSeenReplace == 0 ) { | ||
+ | console.log(" | ||
+ | DJIUpgradeNotifyViewModelnotifyHiddenSeenReplace = 1; | ||
+ | } | ||
+ | } else { | ||
+ | if ( 00 == 0 || DJIUpgradeNotifyViewModelnotifyHiddenSeenHit == 0 ) { | ||
+ | console.log(" | ||
+ | DJIUpgradeNotifyViewModelnotifyHiddenSeenHit = 1; | ||
+ | } | ||
+ | } | ||
+ | return 1; | ||
+ | }); | ||
+ | |||
+ | // | ||
+ | // DJIProductManager currentProductCode jsfunctioniffcc | ||
+ | // | ||
+ | |||
+ | var DJIProductManager = ObjC.classes.DJIProductManager; | ||
+ | var currentProductCode = DJIProductManager[' | ||
+ | var currentProductCodeImpl = currentProductCode.implementation; | ||
+ | currentProductCode.implementation = ObjC.implement(currentProductCode, | ||
+ | var originalResult = currentProductCodeImpl(handle, | ||
+ | if ( originalResult != 13 ) { | ||
+ | if ( 01 == 0 || DJIProductManagercurrentProductCodeSeenReplace == 0 ) { | ||
+ | console.log(" | ||
+ | DJIProductManagercurrentProductCodeSeenReplace = 1; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | if( DJIAppSettingssdr_force_fccSeenReplace == 0 ) { | ||
+ | return 13; //mavic | ||
+ | } else { | ||
+ | return originalResult; | ||
+ | } | ||
+ | }); | ||
+ | } | ||
+ | </ | ||
====== Hooks ====== | ====== Hooks ====== | ||
===== DJITermsNotificationController - shouldShowTerms ===== | ===== DJITermsNotificationController - shouldShowTerms ===== | ||
Line 86: | Line 503: | ||
</ | </ | ||
- | ===== checkIsAdminUser ===== | + | ===== AFSecurityPolicy |
- | <code javascript DJIAccountManager.checkIsAdminUser.js> | + | |
- | if (ObjC.available) { | + | |
- | + | ||
- | var DJIAccountManager = ObjC.classes.DJIAccountManager; | + | |
- | + | ||
- | var checkIsAdminUser = DJIAccountManager[' | + | |
- | var checkIsAdminUserImpl = checkIsAdminUser.implementation; | + | |
- | checkIsAdminUser.implementation = ObjC.implement(checkIsAdminUser, | + | |
- | var originalResult = checkIsAdminUserImpl(handle, | + | |
- | console.log(' | + | |
- | return 1; | + | |
- | }); | + | |
- | } | + | |
- | </ | + | |
- | + | ||
- | ===== setSSLPinningMode ===== | + | |
<code javascript AFSecurityPolicy.setSSLPinningMode.js> | <code javascript AFSecurityPolicy.setSSLPinningMode.js> | ||
if (ObjC.available) { | if (ObjC.available) { | ||
Line 108: | Line 509: | ||
var AFSecurityPolicy = ObjC.classes.AFSecurityPolicy; | var AFSecurityPolicy = ObjC.classes.AFSecurityPolicy; | ||
- | var setSSLPinningMode = AFSecurityPolicy[' | + | var setSSLPinningMode = AFSecurityPolicy[' |
var setSSLPinningModeImpl = setSSLPinningMode.implementation; | var setSSLPinningModeImpl = setSSLPinningMode.implementation; | ||
setSSLPinningMode.implementation = ObjC.implement(setSSLPinningMode, | setSSLPinningMode.implementation = ObjC.implement(setSSLPinningMode, | ||
setSSLPinningModeImpl(handle, | setSSLPinningModeImpl(handle, | ||
console.log(' | console.log(' | ||
+ | }); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== DJIAccountManager - checkIsAdminUser ===== | ||
+ | <code javascript DJIAccountManager.checkIsAdminUser.js> | ||
+ | //Set Admin mode in Flight Records | ||
+ | var DJIAccountManager = ObjC.classes.DJIAccountManager; | ||
+ | var checkIsAdminUser = DJIAccountManager[' | ||
+ | var checkIsAdminUserImpl = checkIsAdminUser.implementation; | ||
+ | checkIsAdminUser.implementation = ObjC.implement(checkIsAdminUser, | ||
+ | var originalResult = checkIsAdminUserImpl(handle, | ||
+ | | ||
+ | | ||
+ | }); | ||
+ | </ | ||
+ | |||
+ | ===== DJIUpgradeNotifyViewModel - notifyHidden ===== | ||
+ | <code javascript DJIUpgradeNotifyViewModel.notifyHidden.js> | ||
+ | //Bypass upgrade notification on splash screen | ||
+ | var DJIUpgradeNotifyViewModel = ObjC.classes.DJIUpgradeNotifyViewModel; | ||
+ | var notifyHidden = DJIUpgradeNotifyViewModel[' | ||
+ | var notifyHiddenImpl = notifyHidden.implementation; | ||
+ | notifyHidden.implementation = ObjC.implement(notifyHidden, | ||
+ | var originalResult = notifyHiddenImpl(handle, | ||
+ | | ||
+ | | ||
+ | }); | ||
+ | </ | ||
+ | |||
+ | ===== DJIAppSettings - sdr_force_fcc ===== | ||
+ | <code javascript DJIAppSettings.sdr_force_fcc.js> | ||
+ | //Force FCC Mode | ||
+ | var shown=0; | ||
+ | var fcc_enabled; | ||
+ | |||
+ | var DJIAppSettings = ObjC.classes.DJIAppSettings; | ||
+ | var sdr_force_fcc = DJIAppSettings[' | ||
+ | var sdr_force_fccImpl = sdr_force_fcc.implementation; | ||
+ | sdr_force_fcc.implementation = ObjC.implement(sdr_force_fcc, | ||
+ | var originalResult = sdr_force_fccImpl(handle, | ||
+ | | ||
+ | | ||
+ | | ||
+ | }); | ||
+ | |||
+ | //Fake Mavic for P4 FCC | ||
+ | var DJIProductManager = ObjC.classes.DJIProductManager; | ||
+ | var currentProductCode = DJIProductManager[' | ||
+ | var currentProductCodeImpl = currentProductCode.implementation; | ||
+ | currentProductCode.implementation = ObjC.implement(currentProductCode, | ||
+ | var originalResult = currentProductCodeImpl(handle, | ||
+ | | ||
+ | console.log(' | ||
+ | shown=1; | ||
+ | } | ||
+ | | ||
+ | | ||
+ | else | ||
+ | | ||
+ | }); | ||
+ | </ | ||
+ | |||
+ | ===== DJIAppForceUpdateManager - hasChecked ===== | ||
+ | <code javascript DJIAppForceUpdateManager.hasChecked.js> | ||
+ | //Bypass DJI Go 4 new app version check | ||
+ | var DJIAppForceUpdateManager = ObjC.classes.DJIAppForceUpdateManager; | ||
+ | var hasChecked = DJIAppForceUpdateManager[' | ||
+ | var hasCheckedImpl = hasChecked.implementation; | ||
+ | hasChecked.implementation = ObjC.implement(hasChecked, | ||
+ | var originalResult = hasCheckedImpl(handle, | ||
+ | | ||
+ | | ||
+ | }); | ||
+ | </ | ||
+ | |||
+ | ===== DJIAppSettings - canUseIllegalChannels ===== | ||
+ | <code javascript DJIAppForceUpdateManager.hasChecked.js> | ||
+ | //Enable Illegal Channels (32 Channels) | ||
+ | if (ObjC.available) { | ||
+ | var DJIAppSettings = ObjC.classes.DJIAppSettings; | ||
+ | var canUseIllegalChannels = DJIAppSettings[' | ||
+ | var canUseIllegalChannelsImpl = canUseIllegalChannels.implementation; | ||
+ | | ||
+ | var originalResult = canUseIllegalChannelsImpl(handle, | ||
+ | console.log(' | ||
+ | return 1; | ||
+ | }); | ||
+ | var DJIRadioLogic = ObjC.classes.DJIRadioLogic; | ||
+ | var canUseIllegalChannels = DJIRadioLogic[' | ||
+ | var canUseIllegalChannelsImpl = canUseIllegalChannels.implementation; | ||
+ | | ||
+ | var originalResult = canUseIllegalChannelsImpl(handle, | ||
+ | console.log(' | ||
+ | return 1; | ||
}); | }); | ||
} | } | ||
</ | </ |