From 2a94e738deaee1f030e2040d5bf4d588985f2355 Mon Sep 17 00:00:00 2001 From: Ke Ming Jiang <389185764@qq.com> Date: Mon, 1 Feb 2021 17:56:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=BA=20OCRunner=20=E6=B7=BB=E5=8A=A0=20rev?= =?UTF-8?q?erse=20=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OCRunner/ORInterpreter.h | 2 + OCRunner/ORInterpreter.m | 40 ++++++++++- OCRunner/RunEnv/MFMethodMapTable.h | 2 +- OCRunner/RunEnv/MFMethodMapTable.m | 12 ++++ OCRunner/RunEnv/MFPropertyMapTable.h | 2 +- OCRunner/RunEnv/MFPropertyMapTable.m | 9 ++- OCRunner/RunEnv/MFStaticVarTable.h | 2 +- OCRunner/RunEnv/MFStaticVarTable.m | 6 +- OCRunner/RunEnv/ORStructDeclare.h | 2 + OCRunner/RunEnv/ORStructDeclare.m | 8 ++- OCRunner/RunnerClasses+Execute.m | 66 +++++++++--------- OCRunnerDemo/OCRunnerDemo/AppDelegate.m | 4 ++ OCRunnerDemo/OCRunnerDemo/ViewController.m | 5 +- RunnerClasses+Reverse.h | 19 ++++++ RunnerClasses+Reverse.m | 79 ++++++++++++++++++++++ oc2mango | 2 +- 16 files changed, 215 insertions(+), 45 deletions(-) create mode 100644 RunnerClasses+Reverse.h create mode 100644 RunnerClasses+Reverse.m diff --git a/OCRunner/ORInterpreter.h b/OCRunner/ORInterpreter.h index 9894d3c..f6f949c 100644 --- a/OCRunner/ORInterpreter.h +++ b/OCRunner/ORInterpreter.h @@ -10,9 +10,11 @@ NS_ASSUME_NONNULL_BEGIN @interface ORInterpreter : NSObject ++ (instancetype)shared; + (void)excuteBinaryPatchFile:(NSString *)path; + (void)excuteJsonPatchFile:(NSString *)path; + (void)excuteNodes:(NSArray *)nodes; ++ (void)reverse; @end NS_ASSUME_NONNULL_END diff --git a/OCRunner/ORInterpreter.m b/OCRunner/ORInterpreter.m index d41b845..d024239 100644 --- a/OCRunner/ORInterpreter.m +++ b/OCRunner/ORInterpreter.m @@ -8,13 +8,28 @@ #import "ORInterpreter.h" #import "RunnerClasses+Execute.h" +#import "RunnerClasses+Reverse.h" #import "MFScopeChain.h" #import "ORSearchedFunction.h" #import "MFValue.h" #import "ORStructDeclare.h" #import "ORSystemFunctionPointerTable.h" +#import "MFStaticVarTable.h" + +@interface ORInterpreter() +@property (nonatomic, copy)NSArray *currentNodes; +@end @implementation ORInterpreter + ++ (instancetype)shared{ + static dispatch_once_t onceToken; + static ORInterpreter *_instance = nil; + dispatch_once(&onceToken, ^{ + _instance = [ORInterpreter new]; + }); + return _instance; +} + (void)excuteBinaryPatchFile:(NSString *)path{ //加载补丁文件 ORPatchFile *file = [ORPatchFile loadBinaryPatch:path]; @@ -38,6 +53,9 @@ + (void)excuteJsonPatchFile:(NSString *)path{ } + (void)excuteNodes:(NSArray *)nodes{ + + ORInterpreter.shared.currentNodes = nodes; + MFScopeChain *scope = [MFScopeChain topScope]; //添加函数、变量等 @@ -58,9 +76,12 @@ + (NSArray *)linkFunctions:(NSArray *)nodes scope:(MFScopeChain *)scope{ for (id expression in nodes) { if ([expression isKindOfClass:[ORDeclareExpression class]]) { ORTypeVarPair *pair = [(ORDeclareExpression *)expression pair]; + NSString *name = pair.var.varname; if ([pair.var isKindOfClass:[ORFuncVariable class]]) { - [funcVars addObject:pair]; - [names addObject:pair.var.varname]; + if ([ORGlobalFunctionTable.shared getFunctionNodeWithName:name] == nil) { + [funcVars addObject:pair]; + [names addObject:name]; + } continue; } } @@ -102,4 +123,19 @@ + (NSArray *)linkFunctions:(NSArray *)nodes scope:(MFScopeChain *)scope{ #endif return normalStatements; } + ++ (void)reverse{ + if (ORInterpreter.shared.currentNodes == nil) { + return; + } + for (ORNode *node in ORInterpreter.shared.currentNodes) { + [node reverse]; + } + [[MFScopeChain topScope] clear]; + [[MFStaticVarTable shareInstance] clear]; + [[ORStructDeclareTable shareInstance] clear]; + [[ORTypeSymbolTable shareInstance] clear]; + ORInterpreter.shared.currentNodes = [NSArray array]; + +} @end diff --git a/OCRunner/RunEnv/MFMethodMapTable.h b/OCRunner/RunEnv/MFMethodMapTable.h index 402ea13..dbe37b1 100644 --- a/OCRunner/RunEnv/MFMethodMapTable.h +++ b/OCRunner/RunEnv/MFMethodMapTable.h @@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN @interface MFMethodMapTable : NSObject + (instancetype)shareInstance; - +- (void)removeMethodsForClass:(Class)clazz; - (void)addMethodMapTableItem:(MFMethodMapTableItem *)methodMapTableItem; - (nullable MFMethodMapTableItem *)getMethodMapTableItemWith:(Class)clazz classMethod:(BOOL)classMethod sel:(SEL)sel; diff --git a/OCRunner/RunEnv/MFMethodMapTable.m b/OCRunner/RunEnv/MFMethodMapTable.m index 29c3b73..667a835 100644 --- a/OCRunner/RunEnv/MFMethodMapTable.m +++ b/OCRunner/RunEnv/MFMethodMapTable.m @@ -67,6 +67,18 @@ - (void)addMethodMapTableItem:(MFMethodMapTableItem *)methodMapTableItem{ CFDictionarySetValue(classMap, (__bridge CFStringRef)(sel), (__bridge const void *)(methodMapTableItem)); } } +- (void)removeMethodsForClass:(Class)clazz{ + if (clazz == NULL) return; + const void *key = (__bridge const void *)clazz; + if (CFDictionaryGetValue(classMethodCache, key)) { + CFRelease(CFDictionaryGetValue(classMethodCache, key)); + } + if (CFDictionaryGetValue(instanceMethodCache, key)) { + CFRelease(CFDictionaryGetValue(instanceMethodCache, key)); + } + CFDictionaryRemoveValue(classMethodCache, key); + CFDictionaryRemoveValue(instanceMethodCache, key); +} - (MFMethodMapTableItem *)getMethodMapTableItemWith:(Class)clazz classMethod:(BOOL)classMethod sel:(SEL)sel{ if (clazz == NULL) { return nil; } diff --git a/OCRunner/RunEnv/MFPropertyMapTable.h b/OCRunner/RunEnv/MFPropertyMapTable.h index 80983fb..6797f85 100644 --- a/OCRunner/RunEnv/MFPropertyMapTable.h +++ b/OCRunner/RunEnv/MFPropertyMapTable.h @@ -29,7 +29,7 @@ NSMutableDictionary *_dic; - (void)addPropertyMapTableItem:(MFPropertyMapTableItem *)propertyMapTableItem; - (nullable MFPropertyMapTableItem *)getPropertyMapTableItemWith:(Class)clazz name:(NSString *)name; - +- (void)removePropertiesForClass:(Class)clazz; @end diff --git a/OCRunner/RunEnv/MFPropertyMapTable.m b/OCRunner/RunEnv/MFPropertyMapTable.m index 9c624a1..294ad00 100644 --- a/OCRunner/RunEnv/MFPropertyMapTable.m +++ b/OCRunner/RunEnv/MFPropertyMapTable.m @@ -69,5 +69,12 @@ - (MFPropertyMapTableItem *)getPropertyMapTableItemWith:(Class)clazz name:(NSStr return CFDictionaryGetValue(propertyMap, (__bridge CFStringRef)(name)); } - +- (void)removePropertiesForClass:(Class)clazz{ + if (clazz == nil) return; + const void *key = (__bridge const void *)clazz; + if (CFDictionaryGetValue(_propertyCache, key)) { + CFRelease(CFDictionaryGetValue(_propertyCache, key)); + } + CFDictionaryRemoveValue(_propertyCache, key); +} @end diff --git a/OCRunner/RunEnv/MFStaticVarTable.h b/OCRunner/RunEnv/MFStaticVarTable.h index 33a4d0d..23b026d 100644 --- a/OCRunner/RunEnv/MFStaticVarTable.h +++ b/OCRunner/RunEnv/MFStaticVarTable.h @@ -17,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN - (nullable MFValue *)getStaticVarValueWithKey:(NSString *)key; - (void)setStaticVarValue:(MFValue *)value withKey:(NSString *)key; - +- (void)clear; @end NS_ASSUME_NONNULL_END diff --git a/OCRunner/RunEnv/MFStaticVarTable.m b/OCRunner/RunEnv/MFStaticVarTable.m index a503a90..664f2ed 100644 --- a/OCRunner/RunEnv/MFStaticVarTable.m +++ b/OCRunner/RunEnv/MFStaticVarTable.m @@ -46,5 +46,9 @@ - (void)setStaticVarValue:(MFValue *)value withKey:(NSString *)key{ _dic[key] = value; [_lock unlock]; } - +- (void)clear{ + [_lock lock]; + _dic = [NSMutableDictionary dictionary]; + [_lock unlock]; +} @end diff --git a/OCRunner/RunEnv/ORStructDeclare.h b/OCRunner/RunEnv/ORStructDeclare.h index fc060d0..4cb9be8 100644 --- a/OCRunner/RunEnv/ORStructDeclare.h +++ b/OCRunner/RunEnv/ORStructDeclare.h @@ -27,6 +27,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)addAlias:(NSString *)alias forStructTypeEncode:(const char *)typeEncode; - (void)addStructDeclare:(ORStructDeclare *)structDeclare; - (nullable ORStructDeclare *)getStructDeclareWithName:(NSString *)name; +- (void)clear; @end @@ -45,5 +46,6 @@ NS_ASSUME_NONNULL_BEGIN - (void)addTypePair:(ORTypeVarPair *)item forAlias:(NSString *)alias; - (void)addSybolItem:(ORSymbolItem *)item forAlias:(NSString *)alias; - (ORSymbolItem *)symbolItemForTypeName:(NSString *)typeName; +- (void)clear; @end NS_ASSUME_NONNULL_END diff --git a/OCRunner/RunEnv/ORStructDeclare.m b/OCRunner/RunEnv/ORStructDeclare.m index 7ac3706..0561c26 100644 --- a/OCRunner/RunEnv/ORStructDeclare.m +++ b/OCRunner/RunEnv/ORStructDeclare.m @@ -73,7 +73,9 @@ - (void)dealloc @implementation ORStructDeclareTable{ NSMutableDictionary *_cache; } - +- (void)clear{ + _cache = [NSMutableDictionary dictionary]; +} - (instancetype)init{ if (self = [super init]) { _cache = [NSMutableDictionary dictionary]; @@ -127,7 +129,9 @@ @implementation ORTypeSymbolTable{ NSMutableDictionary *_table; NSLock *_lock; } - +- (void)clear{ + _table = [NSMutableDictionary dictionary]; +} - (instancetype)init{ if (self = [super init]) { _table = [NSMutableDictionary dictionary]; diff --git a/OCRunner/RunnerClasses+Execute.m b/OCRunner/RunnerClasses+Execute.m index b536abe..8b17a04 100644 --- a/OCRunner/RunnerClasses+Execute.m +++ b/OCRunner/RunnerClasses+Execute.m @@ -93,50 +93,45 @@ void ORDebugFrameStackPop(void){ [invocation getReturnValue:retValuePtr]; return [[MFValue alloc] initTypeEncode:retType pointer:retValuePtr];; } - -static void replace_method(Class clazz, ORMethodImplementation *methodImp, MFScopeChain *scope){ - ORMethodDeclare *declare = methodImp.declare; - NSString *methodName = declare.selectorName; - SEL sel = NSSelectorFromString(methodName); - - BOOL needFreeTypeEncoding = NO; - const char *typeEncoding; +void or_method_replace(BOOL isClassMethod, Class clazz, SEL sel, IMP imp, const char *typeEncode){ Method ocMethod; - if (methodImp.declare.isClassMethod) { + if (isClassMethod) { ocMethod = class_getClassMethod(clazz, sel); }else{ ocMethod = class_getInstanceMethod(clazz, sel); } if (ocMethod) { - typeEncoding = method_getTypeEncoding(ocMethod); - }else{ - typeEncoding = methodImp.declare.returnType.typeEncode; - needFreeTypeEncoding = YES; - typeEncoding = mf_str_append(typeEncoding, "@:"); //add self and _cmd - for (ORTypeVarPair *pair in methodImp.declare.parameterTypes) { - const char *paramTypeEncoding = pair.typeEncode; - const char *beforeTypeEncoding = typeEncoding; - typeEncoding = mf_str_append(typeEncoding, paramTypeEncoding); - free((void *)beforeTypeEncoding); - } + typeEncode = method_getTypeEncoding(ocMethod); } - Class c2 = methodImp.declare.isClassMethod ? objc_getMetaClass(class_getName(clazz)) : clazz; + Class c2 = isClassMethod ? objc_getMetaClass(class_getName(clazz)) : clazz; if (class_respondsToSelector(c2, sel)) { - NSString *orgSelName = [NSString stringWithFormat:@"ORG%@",methodName]; + NSString *orgSelName = [NSString stringWithFormat:@"ORG%@", NSStringFromSelector(sel)]; SEL orgSel = NSSelectorFromString(orgSelName); if (!class_respondsToSelector(c2, orgSel)) { - class_addMethod(c2, orgSel, method_getImplementation(ocMethod), typeEncoding); + class_addMethod(c2, orgSel, method_getImplementation(ocMethod), typeEncode); } } - + class_replaceMethod(c2, sel, imp, typeEncode); +} +static void replace_method(Class clazz, ORMethodImplementation *methodImp){ + const char *typeEncoding = methodImp.declare.returnType.typeEncode; + typeEncoding = mf_str_append(typeEncoding, "@:"); //add self and _cmd + for (ORTypeVarPair *pair in methodImp.declare.parameterTypes) { + const char *paramTypeEncoding = pair.typeEncode; + const char *beforeTypeEncoding = typeEncoding; + typeEncoding = mf_str_append(typeEncoding, paramTypeEncoding); + free((void *)beforeTypeEncoding); + } + Class c2 = methodImp.declare.isClassMethod ? objc_getMetaClass(class_getName(clazz)) : clazz; MFMethodMapTableItem *item = [[MFMethodMapTableItem alloc] initWithClass:c2 method:methodImp]; [[MFMethodMapTable shareInstance] addMethodMapTableItem:item]; + ORMethodDeclare *declare = methodImp.declare; void *imp = register_method(&methodIMP, declare.parameterTypes, declare.returnType, (__bridge_retained void *)methodImp); - class_replaceMethod(c2, sel, imp, typeEncoding); - if (needFreeTypeEncoding) { - free((void *)typeEncoding); - } + + SEL sel = NSSelectorFromString(methodImp.declare.selectorName); + or_method_replace(methodImp.declare.isClassMethod, clazz, sel, imp, typeEncoding); + free((void *)typeEncoding); } static void replace_getter_method(Class clazz, ORPropertyDeclare *prop){ @@ -144,7 +139,7 @@ static void replace_getter_method(Class clazz, ORPropertyDeclare *prop){ const char *retTypeEncoding = prop.var.typeEncode; const char * typeEncoding = mf_str_append(retTypeEncoding, "@:"); void *imp = register_method(&getterImp, @[], prop.var, (__bridge void *)prop); - class_replaceMethod(clazz, getterSEL, imp, typeEncoding); + or_method_replace(NO, clazz, getterSEL, imp, typeEncoding); free((void *)typeEncoding); } @@ -156,7 +151,7 @@ static void replace_setter_method(Class clazz, ORPropertyDeclare *prop){ const char *prtTypeEncoding = prop.var.typeEncode; const char * typeEncoding = mf_str_append("v@:", prtTypeEncoding); void *imp = register_method(&setterImp, @[prop.var], [ORTypeVarPair typePairWithTypeKind:TypeVoid],(__bridge_retained void *)prop); - class_replaceMethod(clazz, setterSEL, imp, typeEncoding); + or_method_replace(NO, clazz, setterSEL, imp, typeEncoding); free((void *)typeEncoding); } @@ -615,9 +610,8 @@ - ( MFValue *)execute:(MFScopeChain *)scope{ && self.declare.funVar.varname && self.declare.funVar.ptCount == 0) { NSString *funcName = self.declare.funVar.varname; - if ([[ORGlobalFunctionTable shared] getFunctionNodeWithName:funcName] == nil) { - [[ORGlobalFunctionTable shared] setFunctionNode:self WithName:funcName]; - } + // NOTE: 恢复后,再执行时,应该覆盖旧的实现 + [[ORGlobalFunctionTable shared] setFunctionNode:self WithName:funcName]; return [MFValue normalEnd]; } MFScopeChain *current = [MFScopeChain scopeChainWithNext:scope]; @@ -1283,6 +1277,7 @@ - (nullable MFValue *)execute:(MFScopeChain *)scope{ } clazz = objc_allocateClassPair(superClass, self.className.UTF8String, 0); objc_registerClassPair(clazz); + [[MFScopeChain topScope] setValue:[MFValue valueWithClass:clazz] withIndentifier:self.className]; } // 添加Class变量到作用域 [current setValue:[MFValue valueWithClass:clazz] withIndentifier:@"Class"]; @@ -1292,7 +1287,7 @@ - (nullable MFValue *)execute:(MFScopeChain *)scope{ } // 在添加方法,这样可以解决属性的懒加载不生效的问题 for (ORMethodImplementation *method in self.methods) { - replace_method(clazz, method, current); + replace_method(clazz, method); } return nil; } @@ -1391,6 +1386,9 @@ - (nullable MFValue *)execute:(MFScopeChain *)scope{ @implementation ORProtocol (Execute) - (nullable MFValue *)execute:(MFScopeChain *)scope{ + if (NSProtocolFromString(self.protcolName) != nil) { + return [MFValue voidValue]; + } Protocol *protocol = objc_allocateProtocol(self.protcolName.UTF8String); for (NSString *name in self.protocols) { Protocol *superP = NSProtocolFromString(name); diff --git a/OCRunnerDemo/OCRunnerDemo/AppDelegate.m b/OCRunnerDemo/OCRunnerDemo/AppDelegate.m index 6da7c83..2257aa8 100644 --- a/OCRunnerDemo/OCRunnerDemo/AppDelegate.m +++ b/OCRunnerDemo/OCRunnerDemo/AppDelegate.m @@ -22,6 +22,10 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [ORInterpreter excuteBinaryPatchFile:binaryPatchFilePath]; [ORInterpreter excuteJsonPatchFile:jsonPatchFilePath]; +// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ +// [ORInterpreter reverse]; +// }); + #if __x86_64__ && TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC NSLog(@"SIMULATOR"); #endif diff --git a/OCRunnerDemo/OCRunnerDemo/ViewController.m b/OCRunnerDemo/OCRunnerDemo/ViewController.m index 06cf5c6..54670d2 100644 --- a/OCRunnerDemo/OCRunnerDemo/ViewController.m +++ b/OCRunnerDemo/OCRunnerDemo/ViewController.m @@ -47,6 +47,9 @@ - (void)viewDidLoad { } - (void)showNext:(UIBarButtonItem *)sender{ - [self.navigationController pushViewController:[NSClassFromString(@"HotFixController") new] animated:YES]; + UIViewController *vc = [NSClassFromString(@"HotFixController") new]; + if (vc) { + [self.navigationController pushViewController:vc animated:YES]; + } } @end diff --git a/RunnerClasses+Reverse.h b/RunnerClasses+Reverse.h new file mode 100644 index 0000000..4bd4c7c --- /dev/null +++ b/RunnerClasses+Reverse.h @@ -0,0 +1,19 @@ +// +// RunnerClasses+Reverse.h +// OCRunner +// +// Created by Jiang on 2021/2/1. +// + +#import +#import +NS_ASSUME_NONNULL_BEGIN +@protocol OCReverse +- (void)reverse; +@end + +@interface ORNode (Reverse) +- (void)reverse; +@end + +NS_ASSUME_NONNULL_END diff --git a/RunnerClasses+Reverse.m b/RunnerClasses+Reverse.m new file mode 100644 index 0000000..93d3e72 --- /dev/null +++ b/RunnerClasses+Reverse.m @@ -0,0 +1,79 @@ +// +// RunnerClasses+Reverse.m +// OCRunner +// +// Created by Jiang on 2021/2/1. +// + +#import "RunnerClasses+Reverse.h" +#import "MFScopeChain.h" +#import "util.h" +#import "MFMethodMapTable.h" +#import "MFPropertyMapTable.h" +#import "MFVarDeclareChain.h" +#import "MFBlock.h" +#import "MFValue.h" +#import "MFStaticVarTable.h" +#import "ORStructDeclare.h" +#import +#import "ORTypeVarPair+TypeEncode.h" +#import "ORCoreImp.h" +#import "ORSearchedFunction.h" +void reverse_method(BOOL isClassMethod, Class clazz, SEL sel){ + NSString *orgSelName = [NSString stringWithFormat:@"ORG%@",NSStringFromSelector(sel)]; + SEL orgsel = NSSelectorFromString(orgSelName); + Method ocMethod; + if (isClassMethod) { + ocMethod = class_getClassMethod(clazz, orgsel); + }else{ + ocMethod = class_getInstanceMethod(clazz, orgsel); + } + const char *typeEncoding = method_getTypeEncoding(ocMethod); + Class c2 = isClassMethod ? objc_getMetaClass(class_getName(clazz)) : clazz; + IMP orgImp = class_getMethodImplementation(c2, orgsel); + class_replaceMethod(c2, sel, orgImp, typeEncoding); +} + +@implementation ORNode (Reverse) +- (void)reverse{ + +} +@end + +@implementation ORClass (Reverse) +- (void)reverse{ + BOOL isRegisterClass = NO; + MFValue *classValue = [[MFScopeChain topScope] recursiveGetValueWithIdentifier:self.className]; + Class classVar = classValue.classValue; + Class class = NSClassFromString(self.className); + if (classVar != nil && classVar == class) { + isRegisterClass = YES; + } + // FIXME: Reverse时,释放ffi_closure和ffi_type + for (ORMethodImplementation *imp in self.methods) { + SEL sel = NSSelectorFromString(imp.declare.selectorName); + BOOL isClassMethod = imp.declare.isClassMethod; + reverse_method(isClassMethod, class, sel); + CFRelease((__bridge CFTypeRef)(imp)); + } + for (ORPropertyDeclare *prop in self.properties){ + NSString *name = prop.var.var.varname; + NSString *str1 = [[name substringWithRange:NSMakeRange(0, 1)] uppercaseString]; + NSString *str2 = name.length > 1 ? [name substringFromIndex:1] : nil; + SEL getterSEL = NSSelectorFromString(name); + SEL setterSEL = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",str1,str2]); + reverse_method(NO, class, getterSEL); + reverse_method(NO, class, setterSEL); + CFRelease((__bridge CFTypeRef)(prop)); + } + [[MFMethodMapTable shareInstance] removeMethodsForClass:class]; + [[MFPropertyMapTable shareInstance] removePropertiesForClass:class]; + if (isRegisterClass) { + objc_disposeClassPair(class); + } +} + +@end + + + diff --git a/oc2mango b/oc2mango index db769e0..52f8ebe 160000 --- a/oc2mango +++ b/oc2mango @@ -1 +1 @@ -Subproject commit db769e0a96211052c17679bc1434eac4431d5915 +Subproject commit 52f8ebe8d50d8d39db406b0530b384385504c8d4