ISSUE-005
FirebaseSDK读取登录状态失败
常见表现
使用谷歌帐号登录的游戏,第二次打开时出现闪退,或是需要重新登录。
相关源码
- (nullable NSData *)itemWithQuery:(NSDictionary *)query error:(NSError **_Nullable)error {
NSMutableDictionary *returningQuery = [query mutableCopy];
returningQuery[(__bridge id)kSecReturnData] = @YES;
returningQuery[(__bridge id)kSecReturnAttributes] = @YES;
returningQuery[(__bridge id)kSecMatchLimit] = @2;
CFArrayRef result = NULL;
OSStatus status =
SecItemCopyMatching((__bridge CFDictionaryRef)returningQuery, (CFTypeRef *)&result);
if (status == noErr && result != NULL) {
NSArray *items = (__bridge_transfer NSArray *)result;
if (items.count == 0) {
return nil;
}
//...
}
}
原因分析
FirebaseSDK传入参数kSecMatchLimit=2,并期望返回结果是一个NSArray。
但目前PlayCover的代码并没有正确处理kSecMatchLimit=2的情况,PlayCover返回的是一个NSDictionary,类型不匹配导致出错。
解决方法
根本解决方法:修改PlayCover代码。
临时解决方法一:每次打开游戏之前,右键游戏图标,清除PlayChain数据可解决闪退。
临时解决方法二:插入跳转指令,从A处直接跳转到B处。
Firebase SDK旧版本(Objcetive-C):
- (nullable NSData *)itemWithQuery:(NSDictionary *)query error:(NSError **_Nullable)error {
CFArrayRef result = NULL;
OSStatus status =
SecItemCopyMatching((__bridge CFDictionaryRef)returningQuery, (CFTypeRef *)&result);
if (status == noErr && result != NULL) {
NSArray *items = (__bridge_transfer NSArray *)result;
/* --------- A --------- */
if (items.count == 0) {
// throw error
} else if (items.count > 1) {
// log("Keychain query returned multiple results")
}
NSDictionary *item = items[0];
/* --------- B --------- */
return item[(__bridge id)kSecValueData];
}
}
0x4C8A04 STR XZR, [SP,#0x130+result]
0x4C8A08 ADD X1, SP, #0x130+result ; result
0x4C8A0C MOV X0, X19 ; query
0x4C8A10 BL _SecItemCopyMatching
0x4C8A14 MOV X3, X0
0x4C8A18 CBNZ W0, loc_4C8B4C
0x4C8A1C LDR X20, [SP,#0x130+result]
0x4C8A20 CBZ X20, loc_4C8B4C
0x4C8A24 MOV X0, X20
0x4C8A28 BL _objc_msgSend$count ; 改为B #0x110,跳转至0x4C8B38
0x4C8A2C CBZ X0, loc_4C8BCC
0x4C8A30 MOV X0, X20
0x4C8A34 BL _objc_msgSend$count
0x4C8A38 CMP X0, #2
0x4C8A3C B.CC loc_4C8A64
0x4C8B1C loc_4C8B1C
0x4C8B1C MOV X0, X21
0x4C8B20 BL _objc_release
0x4C8B24 MOV X0, X21
0x4C8B28 MOV X2, #0
0x4C8B2C BL _objc_msgSend$objectAtIndexedSubscript_
0x4C8B30 MOV X29, X29
0x4C8B34 BL _objc_retainAutoreleasedReturnValue
0x4C8B38 MOV X21, X0 ; 跳转目标位置
0x4C8B3C ADRP X8, #_kSecValueData_ptr@PAGE
0x4C8B40 LDR X8, [X8,#_kSecValueData_ptr@PAGEOFF]
0x4C8B44 LDR X2, [X8]
0x4C8B48 B loc_4C8BA8
0x4C8BA8 loc_4C8BA8
0x4C8BA8 BL _objc_msgSend$objectForKeyedSubscript_
0x4C8BAC MOV X29, X29
0x4C8BB0 BL _objc_retainAutoreleasedReturnValue
0x4C8BB4 MOV X22, X0
0x4C8BB8 MOV X0, X21
0x4C8BBC BL _objc_release ; 屏蔽这条指令,避免重复调用_objc_release
0x4C8BC0 B loc_4C8C04
Firebase SDK新版本(Swift):
func getItemLegacy(query: [String: Any]) throws -> Data? {
var returningQuery = query
returningQuery[kSecReturnData as String] = true
returningQuery[kSecReturnAttributes as String] = true
returningQuery[kSecMatchLimit as String] = 2
var result: AnyObject?
let status = keychainStorage.get(query: returningQuery, result: &result)
if let items = result as? [[String: Any]], status == noErr {
/* --------- A --------- */
if items.isEmpty {
// throw error
} else if items.count > 1 {
// log("Keychain query returned multiple results")
}
let item = items[0];
/* --------- B --------- */
return item[kSecValueData as String] as? Data
}
}
情况一:存在FirebaseAuth.framework
0x36A94 ADRL X0, _$sSaySDySSypGGMd ; 修改这两行,将[[String: Any]]类型改为[String: Any]类型
0x36A9C ADRL X1, _$sSaySDySSypGGMR ; 在AuthErrorUtils.extractJSONObjectFromString()中能找到[String: Any]的type_metadata
0x36AA4 BL ___swift_instantiateConcreteTypeFromMangledNameV2
0x36AA8 MOV X3, X0
0x36AAC ADRP X8, #_$syXlN_ptr@PAGE
0x36AB0 LDR X8, [X8,#_$syXlN_ptr@PAGEOFF]
0x36AB4 ADD X0, SP, #0xB0+var_90
0x36AB8 ADD X1, SP, #0xB0+var_70
0x36ABC ADD X2, X8, #8
0x36AC0 MOV W4, #6
0x36AC4 BL _swift_dynamicCast
0x36AC8 TBZ W0, #0, loc_36B08
0x36ACC LDR X23, [SP,#0xB0+var_90]
0x36AD0 BL _$s6Darwin5noErrs5Int32Vvg ; noErr.getter
0x36AD4 CMP W20, W0
0x36AD8 B.NE loc_36B00
0x36ADC LDR X8, [X23,#0x10]
0x36AE0 STP X26, X8, [SP,#0xB0+var_A0]
0x36AE4 CMP X8, #1 ; 改为MOV X20, X23
0x36AE8 B.EQ loc_36CB0 ; 跳转至0x36D90
0x36D70 loc_36D70
0x36D70 LDR X8, [X23,#0x10]
0x36D74 LDR X19, [SP,#0xB0+var_A8]
0x36D78 CBZ X8, loc_36F14
0x36D7C LDR X20, [X23,#0x20]
0x36D80 MOV X0, X20
0x36D84 BL _swift_bridgeObjectRetain
0x36D88 MOV X0, X23
0x36D8C BL _swift_bridgeObjectRelease
0x36D90 ADRP X8, #_kSecValueData_ptr@PAGE ; 跳转目标位置
0x36D94 LDR X8, [X8,#_kSecValueData_ptr@PAGEOFF]
0x36D98 LDR X0, [X8]
0x36D9C BL _$sSS10FoundationE36_unconditionallyBridgeFromObjectiveCySSSo8NSStringCSgFZ
情况二:不存在FirebaseAuth.framework,和其他代码编译在一起了
0x6D96C ADRL X0, unk_BD971F8 ; 修改此行,将[[String: Any]]类型改为[String: Any]类型
0x6D974 BL sub_35660 ; 搜索字符串Error while converting assumed plist to dictionary,往前找dynamicCast,可找到[String: Any]的type_metadata
0x6D978 MOV X3, X0
0x6D97C ADRP X8, #_$syXlN_ptr@PAGE
0x6D980 LDR X8, [X8,#_$syXlN_ptr@PAGEOFF]
0x6D984 ADD X2, X8, #8
0x6D988 ADD X0, SP, #0xB0+var_A0
0x6D98C ADD X1, SP, #0xB0+var_80
0x6D990 MOV W4, #6
0x6D994 BL j__swift_dynamicCast
0x6D998 TBZ W0, #0, loc_6D9D0
0x6D99C LDR X22, [SP,#0xB0+var_A0]
0x6D9A0 BL j__$s6Darwin5noErrs5Int32Vvg ; noErr.getter
0x6D9A4 CMP W20, W0
0x6D9A8 B.NE loc_6D9C8
0x6D9AC LDR X28, [X22,#0x10]
0x6D9B0 CMP X28, #1 ; 改为STR X27, [SP](暂存Exception信息)
0x6D9B4 B.EQ loc_6DB88 ; 改为MOV X20, X22
0x6D9B8 CBNZ X28, loc_6DA70 ; 跳转至0x6DD04
0x6DCE4 loc_6DCE4
0x6DCE4 MOV X0, X22
0x6DCE8 BL j__swift_bridgeObjectRelease
0x6DCEC ADRL X1, unk_BD96718
0x6DCF4 ADD X0, SP, #0xB0+var_80
0x6DCF8 BL sub_35728
0x6DCFC MOV X0, X22
0x6DD00 BL j__swift_bridgeObjectRelease
0x6DD04 ADRP X8, #_kSecValueData_ptr@PAGE ; 跳转目标位置
0x6DD08 LDR X8, [X8,#_kSecValueData_ptr@PAGEOFF]
0x6DD0C LDR X0, [X8]
0x6DD10 BL j__$sSS10FoundationE36_unconditionallyBridgeFromObjectiveCySSSo8NSStringCSgFZ