This commit is contained in:
2022-02-05 05:21:43 +08:00
parent e6bfc181de
commit 6a6715268f
11 changed files with 175 additions and 28 deletions

View File

@@ -15,7 +15,7 @@
# Get Startted # Get Startted
- 暂定 1.0 版本,还有很多问题没有解决,暂时不要使用,敬请期待... - 暂定 1.0 版本,可做学习参考但暂时不要 fork 也不要使用,还差 Wiki 没有撰写 demo 没有写完和 API 未提交至 maven 敬请期待...
# License # License

View File

@@ -30,6 +30,12 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = '1.8' jvmTarget = '1.8'
} }
kotlin {
sourceSets.main {
kotlin.srcDir("build/generated/ksp/debug/kotlin")
kotlin.srcDir("build/generated/ksp/release/kotlin")
}
}
} }
dependencies { dependencies {

View File

@@ -1 +1 @@
com.highcapable.yukihookapi.demo.InjectTest_YukiHookXposedInit com.highcapable.yukihookapi.demo.hook.inject.MainInjecter_YukiHookXposedInit

View File

@@ -30,6 +30,7 @@
package com.highcapable.yukihookapi.demo package com.highcapable.yukihookapi.demo
import android.os.Bundle import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
@@ -48,7 +49,7 @@ class MainActivity : AppCompatActivity() {
.setPositiveButton("下一个") { _, _ -> .setPositiveButton("下一个") { _, _ ->
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle("Hook 方法参数测试") .setTitle("Hook 方法参数测试")
.setMessage(test("这是没有更改的文字") + "\n模块是否已激活:${YukiHookModuleStatus.isActive()}") .setMessage(test("这是没有更改的文字") + "\n${a(content = "这是原文")}\n模块是否已激活:${YukiHookModuleStatus.isActive()}")
.setPositiveButton("下一个") { _, _ -> .setPositiveButton("下一个") { _, _ ->
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle("Hook 构造方法测试(stub)") .setTitle("Hook 构造方法测试(stub)")
@@ -57,13 +58,19 @@ class MainActivity : AppCompatActivity() {
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle("Hook 构造方法测试(名称)") .setTitle("Hook 构造方法测试(名称)")
.setMessage(InjectTestName("文字没更改").getString() + "\n模块是否已激活:${YukiHookModuleStatus.isActive()}") .setMessage(InjectTestName("文字没更改").getString() + "\n模块是否已激活:${YukiHookModuleStatus.isActive()}")
.setPositiveButton("完成", null) .setPositiveButton("完成") { _, _ -> toast() }
.show() .show()
}.show() }.show()
}.show() }.show()
}.show() }.show()
} }
// for test
private fun toast() = Toast.makeText(this, "我弹出来了,没有 Hook", Toast.LENGTH_SHORT).show()
// for test
private fun a(content1: String = "前缀", content: String) = "$content1${content}_后面加了一段文字"
// for test // for test
private fun test() = "正常显示的一行文字" private fun test() = "正常显示的一行文字"

View File

@@ -35,6 +35,7 @@ import com.highcapable.yukihookapi.demo.MainActivity
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.type.BundleClass import com.highcapable.yukihookapi.hook.type.BundleClass
import com.highcapable.yukihookapi.hook.type.StringType import com.highcapable.yukihookapi.hook.type.StringType
import com.highcapable.yukihookapi.hook.type.UnitType
// for test // for test
class MainHooker : YukiBaseHooker() { class MainHooker : YukiBaseHooker() {
@@ -54,6 +55,24 @@ class MainHooker : YukiBaseHooker() {
}.set(instance, "这段文字被修改成功了") }.set(instance, "这段文字被修改成功了")
} }
} }
injectMember {
method {
name = "toast"
returnType = UnitType
}
intercept()
}
injectMember {
method {
name = "a"
param(StringType, StringType)
returnType = StringType
}
beforeHook {
args(index = 0).set("改了前面的")
args(index = 1).set("改了后面的")
}
}
injectMember { injectMember {
method { method {
name = "test" name = "test"

View File

@@ -31,28 +31,30 @@ package com.highcapable.yukihookapi.demo.hook.inject
import android.app.AlertDialog import android.app.AlertDialog
import android.widget.Toast import android.widget.Toast
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
import com.highcapable.yukihookapi.demo.BuildConfig import com.highcapable.yukihookapi.demo.BuildConfig
import com.highcapable.yukihookapi.demo.InjectTest import com.highcapable.yukihookapi.demo.InjectTest
import com.highcapable.yukihookapi.demo.MainActivity import com.highcapable.yukihookapi.demo.MainActivity
import com.highcapable.yukihookapi.demo.hook.MainHooker
import com.highcapable.yukihookapi.demo.hook.SecondHooker
import com.highcapable.yukihookapi.hook.factory.encase import com.highcapable.yukihookapi.hook.factory.encase
import com.highcapable.yukihookapi.hook.factory.findMethod import com.highcapable.yukihookapi.hook.factory.findMethod
import com.highcapable.yukihookapi.hook.proxy.YukiHookXposedInitProxy import com.highcapable.yukihookapi.hook.proxy.YukiHookXposedInitProxy
import com.highcapable.yukihookapi.hook.type.ActivityClass import com.highcapable.yukihookapi.hook.type.ActivityClass
import com.highcapable.yukihookapi.hook.type.BundleClass import com.highcapable.yukihookapi.hook.type.BundleClass
import com.highcapable.yukihookapi.hook.type.StringType import com.highcapable.yukihookapi.hook.type.StringType
import com.highcapable.yukihookapi.hook.type.UnitType
// for test // for test
@InjectYukiHookWithXposed @InjectYukiHookWithXposed
class MainInjecter : YukiHookXposedInitProxy { class MainInjecter : YukiHookXposedInitProxy {
override fun onHook() { override fun onHook() {
// 设置模式
YukiHookAPI.isDebug = true
// 方案 1 // 方案 1
encase(MainHooker(), SecondHooker()) // encase(MainHooker(), SecondHooker())
// 方案 2 // 方案 2
encase(BuildConfig.APPLICATION_ID) { encase {
loadApp(name = BuildConfig.APPLICATION_ID) { loadApp(name = BuildConfig.APPLICATION_ID) {
MainActivity::class.java.hook { MainActivity::class.java.hook {
injectMember { injectMember {
@@ -67,6 +69,24 @@ class MainInjecter : YukiHookXposedInitProxy {
}.set(instance, "这段文字被修改成功了") }.set(instance, "这段文字被修改成功了")
} }
} }
injectMember {
method {
name = "toast"
returnType = UnitType
}
intercept()
}
injectMember {
method {
name = "a"
param(StringType, StringType)
returnType = StringType
}
beforeHook {
args(index = 0).set("改了前面的")
args(index = 1).set("改了后面的")
}
}
injectMember { injectMember {
method { method {
name = "test" name = "test"

View File

@@ -52,6 +52,17 @@ class YukiHookXposedProcessor : SymbolProcessorProvider {
/** 插入 Xposed 尾部的名称 */ /** 插入 Xposed 尾部的名称 */
private val xposedClassShortName = "_YukiHookXposedInit" private val xposedClassShortName = "_YukiHookXposedInit"
/**
* 获取父类名称 - 只取最后一个
* @return [String]
*/
private val KSClassDeclaration.superName
get() = try {
superTypes.last().element.toString()
} catch (_: Exception) {
""
}
/** /**
* 创建一个环境方法体方便调用 * 创建一个环境方法体方便调用
* @param env 方法体 * @param env 方法体
@@ -72,14 +83,16 @@ class YukiHookXposedProcessor : SymbolProcessorProvider {
resolver.getSymbolsWithAnnotation(InjectYukiHookWithXposed::class.java.name) resolver.getSymbolsWithAnnotation(InjectYukiHookWithXposed::class.java.name)
.asSequence() .asSequence()
.filterIsInstance<KSClassDeclaration>().forEach { .filterIsInstance<KSClassDeclaration>().forEach {
if (injectOnce) { if (injectOnce)
injectAssets( if (it.superName == "YukiHookXposedInitProxy") {
codePath = (it.location as? FileLocation?)?.filePath ?: "", injectAssets(
packageName = it.packageName.asString(), codePath = (it.location as? FileLocation?)?.filePath ?: "",
className = it.simpleName.asString() packageName = it.packageName.asString(),
) className = it.simpleName.asString()
injectClass(it.packageName.asString(), it.simpleName.asString()) )
} else logger.error(message = "@InjectYukiHookWithXposed only can be use in once times") injectClass(it.packageName.asString(), it.simpleName.asString())
} else logger.error(message = "HookEntryClass \"${it.simpleName.asString()}\" must be implements YukiHookXposedInitProxy")
else logger.error(message = "@InjectYukiHookWithXposed only can be use in once times")
/** 仅处理第一个标记的类 - 再次处理将拦截并报错 */ /** 仅处理第一个标记的类 - 再次处理将拦截并报错 */
injectOnce = false injectOnce = false
} }

View File

@@ -52,6 +52,15 @@ object YukiHookAPI {
/** 全局标识 */ /** 全局标识 */
const val TAG = "YukiHookAPI" const val TAG = "YukiHookAPI"
/**
* 是否开启调试模式 - 默认启用
*
* 启用后将交由日志输出管理器打印详细 Hook 日志到控制台
*
* 请过滤 [TAG] (YukiHookAPI) 即可找到每条日志
*/
var isDebug = true
/** /**
* Xposed Hook API 绑定的模块包名 - 未写将自动生成 * Xposed Hook API 绑定的模块包名 - 未写将自动生成
* - 你不应该设置此变量的名称 - 请使用 [encase] 装载模块包名 * - 你不应该设置此变量的名称 - 请使用 [encase] 装载模块包名

View File

@@ -29,13 +29,16 @@
package com.highcapable.yukihookapi.hook.core package com.highcapable.yukihookapi.hook.core
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.annotation.DoNotUseMethod import com.highcapable.yukihookapi.annotation.DoNotUseMethod
import com.highcapable.yukihookapi.hook.core.finder.ConstructorFinder import com.highcapable.yukihookapi.hook.core.finder.ConstructorFinder
import com.highcapable.yukihookapi.hook.core.finder.FieldFinder import com.highcapable.yukihookapi.hook.core.finder.FieldFinder
import com.highcapable.yukihookapi.hook.core.finder.MethodFinder import com.highcapable.yukihookapi.hook.core.finder.MethodFinder
import com.highcapable.yukihookapi.hook.log.loggerE import com.highcapable.yukihookapi.hook.log.loggerE
import com.highcapable.yukihookapi.hook.log.loggerI
import com.highcapable.yukihookapi.hook.param.HookParam import com.highcapable.yukihookapi.hook.param.HookParam
import com.highcapable.yukihookapi.hook.param.PackageParam import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.utils.runBlocking
import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XC_MethodReplacement import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedBridge
@@ -120,7 +123,7 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
* 你只能使用一次 [method] 或 [constructor] 方法 - 否则结果会被替换 * 你只能使用一次 [method] 或 [constructor] 方法 - 否则结果会被替换
* @param initiate 方法体 * @param initiate 方法体
*/ */
fun method(initiate: MethodFinder.() -> Unit) { fun method(initiate: MethodFinder.() -> Unit) = runBlocking {
runCatching { runCatching {
member = MethodFinder(hookClass).apply(initiate).find() member = MethodFinder(hookClass).apply(initiate).find()
}.onFailure { }.onFailure {
@@ -129,7 +132,7 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
onAllFailureCallback?.invoke(it) onAllFailureCallback?.invoke(it)
if (onNoSuchMemberCallback == null && onAllFailureCallback == null) onHookFailureMsg(it) if (onNoSuchMemberCallback == null && onAllFailureCallback == null) onHookFailureMsg(it)
} }
} }.result { onHookLogMsg(msg = "Find Method [$member] takes ${it}ms") }
/** /**
* 查找需要 Hook 的构造类 * 查找需要 Hook 的构造类
@@ -137,7 +140,7 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
* 你只能使用一次 [method] 或 [constructor] 方法 - 否则结果会被替换 * 你只能使用一次 [method] 或 [constructor] 方法 - 否则结果会被替换
* @param initiate 方法体 * @param initiate 方法体
*/ */
fun constructor(initiate: ConstructorFinder.() -> Unit) { fun constructor(initiate: ConstructorFinder.() -> Unit) = runBlocking {
runCatching { runCatching {
member = ConstructorFinder(hookClass).apply(initiate).find() member = ConstructorFinder(hookClass).apply(initiate).find()
}.onFailure { }.onFailure {
@@ -146,7 +149,7 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
onAllFailureCallback?.invoke(it) onAllFailureCallback?.invoke(it)
if (onNoSuchMemberCallback == null && onAllFailureCallback == null) onHookFailureMsg(it) if (onNoSuchMemberCallback == null && onAllFailureCallback == null) onHookFailureMsg(it)
} }
} }.result { onHookLogMsg(msg = "Find Constructor [$member] takes ${it}ms") }
/** /**
* 查找 [Field] * 查找 [Field]
@@ -155,7 +158,11 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
*/ */
fun HookParam.field(initiate: FieldFinder.() -> Unit) = fun HookParam.field(initiate: FieldFinder.() -> Unit) =
try { try {
FieldFinder(hookClass).apply(initiate).find() var result: FieldFinder.Result? = null
runBlocking {
result = FieldFinder(hookClass).apply(initiate).find()
}.result { onHookLogMsg(msg = "Find Field [${result?.give()}] takes ${it}ms") }
result!!
} catch (e: Throwable) { } catch (e: Throwable) {
isStopHookMode = true isStopHookMode = true
onNoSuchMemberCallback?.invoke(e) onNoSuchMemberCallback?.invoke(e)
@@ -277,6 +284,8 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
if (baseParam == null) return null if (baseParam == null) return null
return HookParam(baseParam).let { param -> return HookParam(baseParam).let { param ->
try { try {
if (replaceHookCallback != null)
onHookLogMsg(msg = "Replace Hook Member [${member}] done")
replaceHookCallback?.invoke(param) replaceHookCallback?.invoke(param)
} catch (e: Throwable) { } catch (e: Throwable) {
onConductFailureCallback?.invoke(param, e) onConductFailureCallback?.invoke(param, e)
@@ -295,6 +304,8 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
HookParam(baseParam).also { param -> HookParam(baseParam).also { param ->
runCatching { runCatching {
beforeHookCallback?.invoke(param) beforeHookCallback?.invoke(param)
if (beforeHookCallback != null)
onHookLogMsg(msg = "Before Hook Member [${member}] done")
}.onFailure { }.onFailure {
onConductFailureCallback?.invoke(param, it) onConductFailureCallback?.invoke(param, it)
onAllFailureCallback?.invoke(it) onAllFailureCallback?.invoke(it)
@@ -309,6 +320,8 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
HookParam(baseParam).also { param -> HookParam(baseParam).also { param ->
runCatching { runCatching {
afterHookCallback?.invoke(param) afterHookCallback?.invoke(param)
if (afterHookCallback != null)
onHookLogMsg(msg = "After Hook Member [${member}] done")
}.onFailure { }.onFailure {
onConductFailureCallback?.invoke(param, it) onConductFailureCallback?.invoke(param, it)
onAllFailureCallback?.invoke(it) onAllFailureCallback?.invoke(it)
@@ -333,6 +346,14 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
private fun onHookFailureMsg(throwable: Throwable) = private fun onHookFailureMsg(throwable: Throwable) =
loggerE(msg = "Try to hook $hookClass[$member] got an Exception", e = throwable) loggerE(msg = "Try to hook $hookClass[$member] got an Exception", e = throwable)
/**
* Hook 过程中开启了 [YukiHookAPI.isDebug] 输出调试信息
* @param msg 调试日志内容
*/
private fun onHookLogMsg(msg: String) {
if (YukiHookAPI.isDebug) loggerI(msg = msg)
}
override fun toString() = "$member#YukiHook" override fun toString() = "$member#YukiHook"
/** /**

View File

@@ -171,7 +171,7 @@ public class ReflectionUtils {
* @param methodName 方法名 * @param methodName 方法名
*/ */
public static Method findMethodNoParam(Class<?> clazz, Class<?> returnType, String methodName) { public static Method findMethodNoParam(Class<?> clazz, Class<?> returnType, String methodName) {
String fullMethodName = clazz.getName() + '#' + methodName + returnType + "#exact#NoParam"; String fullMethodName = "name:[" + methodName + "] in Class [" + clazz.getName() + "] by YukiHook#finder";
if (!methodCache.containsKey(fullMethodName)) { if (!methodCache.containsKey(fullMethodName)) {
Method method = findMethodIfExists(clazz, returnType, methodName); Method method = findMethodIfExists(clazz, returnType, methodName);
methodCache.put(fullMethodName, method); methodCache.put(fullMethodName, method);
@@ -190,7 +190,7 @@ public class ReflectionUtils {
* @param parameterTypes 方法参数类型数组 * @param parameterTypes 方法参数类型数组
*/ */
public static Method findMethodBestMatch(Class<?> clazz, Class<?> returnType, String methodName, Class<?>... parameterTypes) { public static Method findMethodBestMatch(Class<?> clazz, Class<?> returnType, String methodName, Class<?>... parameterTypes) {
String fullMethodName = clazz.getName() + '#' + methodName + returnType + getParametersString(parameterTypes) + "#exact"; String fullMethodName = "name:[" + methodName + "] paramType:[" + getParametersString(parameterTypes) + "] in Class [" + clazz.getName() + "] by YukiHook#finder";
if (!methodCache.containsKey(fullMethodName)) { if (!methodCache.containsKey(fullMethodName)) {
Method method = findMethodIfExists(clazz, returnType, methodName, parameterTypes); Method method = findMethodIfExists(clazz, returnType, methodName, parameterTypes);
methodCache.put(fullMethodName, method); methodCache.put(fullMethodName, method);
@@ -207,24 +207,24 @@ public class ReflectionUtils {
* @param parameterTypes 构造类方法参数类型数组 * @param parameterTypes 构造类方法参数类型数组
*/ */
public static Constructor<?> findConstructorExact(Class<?> clazz, Class<?>... parameterTypes) { public static Constructor<?> findConstructorExact(Class<?> clazz, Class<?>... parameterTypes) {
String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#exact"; String fullConstructorName = "paramType:[" + getParametersString(parameterTypes) + "in Class [" + clazz.getName() + "] by YukiHook#finder";
try { try {
Constructor<?> constructor = clazz.getDeclaredConstructor(parameterTypes); Constructor<?> constructor = clazz.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true); constructor.setAccessible(true);
return constructor; return constructor;
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
throw new NoSuchMethodError("Can't find constructor " + fullConstructorName); throw new NoSuchMethodError("Can't find this constructor --> " + fullConstructorName);
} }
} }
private static Method findMethodExact(Class<?> clazz, String methodName, Class<?>... parameterTypes) { private static Method findMethodExact(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#zYukiHook#exact"; String fullMethodName = "name:[" + methodName + "] paramType:[" + getParametersString(parameterTypes) + "] in Class [" + clazz.getName() + "] by YukiHook#finder";
try { try {
Method method = clazz.getDeclaredMethod(methodName, parameterTypes); Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
method.setAccessible(true); method.setAccessible(true);
return method; return method;
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
throw new NoSuchMethodError("Can't find method " + fullMethodName); throw new NoSuchMethodError("Can't find this method --> " + fullMethodName);
} }
} }
@@ -238,6 +238,6 @@ public class ReflectionUtils {
for (Method method : methods) if (method.getName().equals(methodName)) return method; for (Method method : methods) if (method.getName().equals(methodName)) return method;
} while ((clz = clz.getSuperclass()) != null); } while ((clz = clz.getSuperclass()) != null);
} }
throw new IllegalArgumentException("Method not found <name:" + methodName + " returnType:" + returnType.getName() + " paramType:" + getParametersString(parameterTypes) + "> in Class <" + clazz.getName() + ">"); throw new IllegalArgumentException("Can't find this method --> name:[" + methodName + "] returnType:[" + returnType.getName() + "] paramType:[" + getParametersString(parameterTypes) + "] in Class [" + clazz.getName() + "] by YukiHook#finder");
} }
} }

View File

@@ -0,0 +1,52 @@
/**
* MIT License
*
* Copyright (C) 2022 HighCapable
*
* This file is part of YukiHookAPI.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* This file is Created by fankes on 2022/2/5.
*/
package com.highcapable.yukihookapi.hook.utils
/**
* 计算方法执行耗时
* @param block 方法块
* @return [RunBlockResult]
*/
inline fun <R> runBlocking(block: () -> R): RunBlockResult {
val start = System.currentTimeMillis()
block()
return RunBlockResult(after = System.currentTimeMillis() - start)
}
/**
* 构造耗时计算结果类
* @param after 耗时
*/
class RunBlockResult(private val after: Long) {
/**
* 获取耗时计算结果
* @param initiate 回调结果 - ([Long] 耗时)
*/
fun result(initiate: (Long) -> Unit) = initiate(after)
}