Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7527f91
feat: enable KiouEditor as a Chinlan consumer
tkgstrator Jul 9, 2026
d2a2352
feat: publish entry slots from KiouEditor hook installers
tkgstrator Jul 9, 2026
bbf2c61
refactor: split Hook/FriendUnhide.m into hook body + bridge halves
tkgstrator Jul 9, 2026
b1d4690
fix(hook): unbreak FriendUnhideBridge split — expose shared globals v…
tkgstrator Jul 9, 2026
c8685f9
feat(assist-tune): promote BSE.EvaluateAsync into the shared catalog
tkgstrator Jul 9, 2026
d1c892e
feat(ai-special-support): promote 棋桜覚醒 UI-unlock into the shared catalog
tkgstrator Jul 9, 2026
564fe5a
feat(recipes): let consumers override DYLIB_PATH via KIOU_HOOK_DYLIB_…
tkgstrator Jul 9, 2026
c244a07
fix(kioueditor-rvas): repin KiouEditor RVA macros from 1.0.1 to 1.0.2
tkgstrator Jul 9, 2026
8de810d
fix(friend-unhide): resolve direct-call RVAs via KIOUHookSiteAddr, no…
tkgstrator Jul 9, 2026
f623cd8
feat(common): add KIOU_FEATURE_KIFU_AUTOSAVE to the shared feature enum
tkgstrator Jul 9, 2026
69c5b02
feat(matching-filter): add 3 SITEs for KiouEditor preferred-seat filter
tkgstrator Jul 9, 2026
4e56599
fix(chinlan): publish entry slots for AccountObserve + GrpcLogging in…
tkgstrator Jul 10, 2026
733f30d
chore(diag): expand fire-time logging in match/select/sync hook bodies
tkgstrator Jul 10, 2026
ebbbb8b
fix(match): refresh stale self_user_id from active account on merge
tkgstrator Jul 10, 2026
cbfcf78
refactor(log): unify IPALog format + dedup no-op account saves
tkgstrator Jul 10, 2026
68fc260
feat(common): expose KIOUEditorAssistNodesLimit accessors
tkgstrator Jul 10, 2026
1684f12
feat(match): hook ShogiMatchStreamHandler.SendAsync at 0x5BD00E0
tkgstrator Jul 10, 2026
5a4c4ac
feat(observe): catalog universal gRPC wire-log sites
tkgstrator Jul 10, 2026
79699c0
feat(hook): add NativeSyncSession Search* and SetOption hook sites
tkgstrator Jul 31, 2026
863c5fe
feat(recipes): add KIOU 1.1.0 target and per-version RVA headers
tkgstrator Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 27 additions & 15 deletions Account/Persistence.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ void KIOUSaveAccount(NSString *uuid, NSString *userName, NSString *openId,
NSString *userId, NSString *distinctId) {
if (userId.length == 0) {
IPALog([NSString stringWithFormat:
@"[ACCOUNT] save skipped: missing userId (uuid=%@ userName=%@)",
@"[ACCOUNT] skipped: reason=missingUserId uuid=%@ userName=%@",
uuid ?: @"", userName ?: @""]);
return;
}
Expand All @@ -59,6 +59,7 @@ void KIOUSaveAccount(NSString *uuid, NSString *userName, NSString *openId,
kFieldDistinctId: distinctId ?: @"",
kFieldSavedAt: @((NSInteger)[[NSDate date] timeIntervalSince1970]),
};
BOOL noopMerge = NO;
for (NSDictionary *e in existing) {
NSString *eId = e[kFieldUserId];
if ([eId isKindOfClass:[NSString class]] && [eId isEqualToString:userId]) {
Expand All @@ -67,6 +68,15 @@ void KIOUSaveAccount(NSString *uuid, NSString *userName, NSString *openId,
if (userName.length == 0) merged[kFieldUserName] = e[kFieldUserName] ?: @"";
if (openId.length == 0) merged[kFieldOpenId] = e[kFieldOpenId] ?: @"";
if (distinctId.length == 0) merged[kFieldDistinctId] = e[kFieldDistinctId] ?: @"";
// Detect a no-op re-save: every non-timestamp field already
// matches. Common when RunLoginSequence and AccountExists both
// fire for the same account within a few dozen ms.
noopMerge =
[merged[kFieldUuid] isEqual:e[kFieldUuid]] &&
[merged[kFieldUserName] isEqual:e[kFieldUserName]] &&
[merged[kFieldOpenId] isEqual:e[kFieldOpenId]] &&
[merged[kFieldUserId] isEqual:e[kFieldUserId]] &&
[merged[kFieldDistinctId] isEqual:e[kFieldDistinctId]];
[next addObject:merged];
replaced = YES;
} else {
Expand All @@ -75,9 +85,11 @@ void KIOUSaveAccount(NSString *uuid, NSString *userName, NSString *openId,
}
if (!replaced) [next addObject:fresh];
[d setObject:next forKey:kKeyAccounts];
IPALog([NSString stringWithFormat:
@"[ACCOUNT] saved userId=%@ userName=%@ uuid=%@ total=%lu",
userId, userName ?: @"", uuid ?: @"", (unsigned long)next.count]);
if (!noopMerge) {
IPALog([NSString stringWithFormat:
@"[ACCOUNT] saved: userId=%@ userName=%@ uuid=%@ total=%lu",
userId, userName ?: @"", uuid ?: @"", (unsigned long)next.count]);
}
kfPostAccountStateChanged();
}

Expand All @@ -104,7 +116,7 @@ void KIOUUpdateAccountProfile(NSString *userId, NSString *openId,
if (!found) return;
[d setObject:next forKey:kKeyAccounts];
IPALog([NSString stringWithFormat:
@"[ACCOUNT] profile updated userId=%@ openId=%@ ranks=%lu",
@"[ACCOUNT] updated: scope=profile userId=%@ openId=%@ ranks=%lu",
userId, openId ?: @"", (unsigned long)ranks.count]);
kfPostAccountStateChanged();
}
Expand All @@ -122,7 +134,7 @@ void KIOUDeleteAccount(NSString *userId) {
}
[d setObject:next forKey:kKeyAccounts];
IPALog([NSString stringWithFormat:
@"[ACCOUNT] deleted userId=%@ remaining=%lu",
@"[ACCOUNT] deleted: userId=%@ remaining=%lu",
userId, (unsigned long)next.count]);
kfPostAccountStateChanged();
}
Expand All @@ -139,7 +151,7 @@ void KIOUSetActiveAccountUserId(NSString *userId) {
} else {
[d setObject:userId forKey:kKeyActiveUserId];
}
IPALog([NSString stringWithFormat:@"[ACCOUNT] active_user_id=%@",
IPALog([NSString stringWithFormat:@"[ACCOUNT] applied: activeUserId=%@",
userId.length > 0 ? userId : @"(none)"]);
kfPostAccountStateChanged();
}
Expand All @@ -156,7 +168,7 @@ void KIOUSetForceRegisterOnNextLaunch(bool enabled) {
} else {
[d removeObjectForKey:kKeyForceRegister];
}
IPALog([NSString stringWithFormat:@"[ACCOUNT] force_register=%s",
IPALog([NSString stringWithFormat:@"[ACCOUNT] applied: forceRegister=%s",
enabled ? "true" : "false"]);
}

Expand All @@ -169,10 +181,10 @@ void KIOUSetPendingDeviceId(NSString *uuid) {
NSUserDefaults *d = [NSUserDefaults standardUserDefaults];
if (uuid.length == 0) {
[d removeObjectForKey:kKeyPendingDeviceId];
IPALog(@"[ACCOUNT] pending_device_id cleared");
IPALog(@"[ACCOUNT] deleted: field=pendingDeviceId");
} else {
[d setObject:uuid forKey:kKeyPendingDeviceId];
IPALog([NSString stringWithFormat:@"[ACCOUNT] pending_device_id=%@", uuid]);
IPALog([NSString stringWithFormat:@"[ACCOUNT] applied: pendingDeviceId=%@", uuid]);
}
kfPostAccountStateChanged();
}
Expand All @@ -186,10 +198,10 @@ void KIOUSetPendingDistinctId(NSString *uuid) {
NSUserDefaults *d = [NSUserDefaults standardUserDefaults];
if (uuid.length == 0) {
[d removeObjectForKey:kKeyPendingDistinctId];
IPALog(@"[ACCOUNT] pending_distinct_id cleared");
IPALog(@"[ACCOUNT] deleted: field=pendingDistinctId");
} else {
[d setObject:uuid forKey:kKeyPendingDistinctId];
IPALog([NSString stringWithFormat:@"[ACCOUNT] pending_distinct_id=%@", uuid]);
IPALog([NSString stringWithFormat:@"[ACCOUNT] applied: pendingDistinctId=%@", uuid]);
}
kfPostAccountStateChanged();
}
Expand All @@ -198,13 +210,13 @@ void KIOUSwitchAccount(NSString *uuid) {
NSString *armedDistinct = KIOUPendingDistinctId();
if (armedDistinct.length > 0) {
IPALog([NSString stringWithFormat:
@"[ACCOUNT] KIOUSwitchAccount refused: Register flow in progress "
@"(pending_distinct_id=%@)", armedDistinct]);
@"[ACCOUNT] skipped: op=switchAccount reason=registerInProgress "
@"pendingDistinctId=%@", armedDistinct]);
return;
}
KIOUSetPendingDeviceId(uuid);
IPALog([NSString stringWithFormat:
@"[ACCOUNT] KIOUSwitchAccount armed pending_device_id=%@", uuid ?: @"(nil)"]);
@"[ACCOUNT] armed: op=switchAccount pendingDeviceId=%@", uuid ?: @"(nil)"]);
}

// ---------------------------------------------------------------------------
Expand Down
63 changes: 34 additions & 29 deletions Hook/AccountObserve.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#import "KIOUHook.h"
#import "Hook/Common.h"
#import "Account/Persistence.h"
#import "il2cpp.h"
#import "logging.h"
Expand All @@ -21,11 +22,10 @@
// TitleMenuPopupPresenter.RunDeleteAccountSequenceAsync (raw site, not in catalog)
// ===========================================================================

// RunReset / RunDelete are not in the catalog (not binpatched / not part
// of the cave system); keep their RVAs local so the hook installer can
// MSHookFunction them on JB.
#define KF_RVA_RUN_RESET_USER_DATA_SEQ 0x5DCC204
#define KF_RVA_RUN_DELETE_ACCOUNT_SEQ 0x5DCC2B4
// RunReset / RunDelete have no cave (they aren't binpatched), so they are
// JB-only MSHookFunction targets. Their addresses still come from the
// catalog — as hook_id = -1 rows — so they track the build's target
// version like everything else.

// ---------------------------------------------------------------------------
// Field offsets
Expand Down Expand Up @@ -136,12 +136,12 @@
void *newStr = g_il2cpp_string_new(pending.UTF8String);
if (newStr) {
IPALog([NSString stringWithFormat:
@"[ACCOUNT] RegisterUserArgs.Create distinctId %@", pending]);
@"[ACCOUNT] applied: at=RegisterUserArgs.Create field=distinctId value=%@", pending]);
return newStr;
}
}
IPALog([NSString stringWithFormat:
@"[ACCOUNT] RegisterUserArgs.Create userName=%@ distinctId=%@",
@"[ACCOUNT] fire: at=RegisterUserArgs.Create userName=%@ distinctId=%@",
readIl2CppStr(userName) ?: @"(nil)",
readIl2CppStr(distinctId) ?: @"(nil)"]);
return distinctId;
Expand All @@ -163,12 +163,12 @@
void *newStr = g_il2cpp_string_new(pending.UTF8String);
if (newStr) {
IPALog([NSString stringWithFormat:
@"[ACCOUNT] LoginArgs.Create deviceId %@", pending]);
@"[ACCOUNT] applied: at=LoginArgs.Create field=deviceId value=%@", pending]);
return newStr;
}
}
IPALog([NSString stringWithFormat:
@"[ACCOUNT] LoginArgs.Create deviceId=%@ distinctId=%@",
@"[ACCOUNT] fire: at=LoginArgs.Create deviceId=%@ distinctId=%@",
readIl2CppStr(deviceId) ?: @"(nil)",
readIl2CppStr(distinctId) ?: @"(nil)"]);
return deviceId;
Expand Down Expand Up @@ -197,7 +197,7 @@ static void observeRunLoginSeqCompletion(void *self) {
NSString *userName = readIl2CppStr(readPtr(candidate, OFF_LOGIN_REPLY_USER_NAME));
if (!userName && !deviceId) continue;
IPALog([NSString stringWithFormat:
@"[ACCOUNT] LoginReply @0x%lx userName=%@ deviceId=%@",
@"[ACCOUNT] resolved: subject=loginReply offset=0x%lx userName=%@ deviceId=%@",
(unsigned long)offsets[i], userName ?: @"(nil)", deviceId ?: @"(nil)"]);

NSString *userId = extractJWTSub(accessToken);
Expand All @@ -218,7 +218,7 @@ void KIOUHookRunLoginSeqMoveNext(void *self, void *mi) {
@try { s_origRunLoginSeqMoveNext(self, mi); }
@catch (NSException *e) {
IPALog([NSString stringWithFormat:
@"[ACCOUNT] RunLoginSeq.MoveNext orig threw: %@", e]);
@"[ACCOUNT] exception: at=RunLoginSeq.MoveNext error=%@", e]);
return;
}
}
Expand All @@ -242,7 +242,7 @@ static void observeGetSelfProfileCompletion(void *self) {
if (userName.length == 0 && openUserId.length == 0) continue;

IPALog([NSString stringWithFormat:
@"[ACCOUNT] SelfProfile @0x%lx userName=%@ openUserId=%@",
@"[ACCOUNT] resolved: subject=selfProfile offset=0x%lx userName=%@ openUserId=%@",
(unsigned long)off, userName ?: @"(nil)", openUserId ?: @"(nil)"]);

NSMutableArray<NSDictionary *> *rankDicts = [NSMutableArray array];
Expand Down Expand Up @@ -279,7 +279,7 @@ void KIOUHookGetSelfProfileMoveNext(void *self, void *mi) {
@try { s_origGetSelfProfileMoveNext(self, mi); }
@catch (NSException *e) {
IPALog([NSString stringWithFormat:
@"[ACCOUNT] GetSelfProfile.MoveNext orig threw: %@", e]);
@"[ACCOUNT] exception: at=GetSelfProfile.MoveNext error=%@", e]);
return;
}
}
Expand Down Expand Up @@ -310,14 +310,14 @@ bool KIOUHookAccountExists(void *data, void *mi) {
if (s_origAccountExists) {
@try { origResult = s_origAccountExists(data, mi); }
@catch (NSException *e) {
IPALog([NSString stringWithFormat:@"[ACCOUNT] AccountExists orig threw: %@", e]);
IPALog([NSString stringWithFormat:@"[ACCOUNT] exception: at=AccountExists error=%@", e]);
}
}
observeAccountExistsData(data);
bool forceRegister = KIOUForceRegisterOnNextLaunch();
bool result = forceRegister ? false : origResult;
if (forceRegister) {
IPALog(@"[ACCOUNT] AccountExists overridden false (force_register)");
IPALog(@"[ACCOUNT] applied: at=AccountExists override=false reason=forceRegister");
}
return result;
}
Expand All @@ -330,13 +330,13 @@ KIOUUniTaskRet KIOUHookRunResetUserDataSeq(void *ct, void *mi) {
KIOUSetPendingDistinctId(freshUuid);
KIOUSetPendingDeviceId(freshUuid);
IPALog([NSString stringWithFormat:
@"[ACCOUNT] RunResetUserDataSequenceAsync armed fresh_uuid=%@", freshUuid]);
@"[ACCOUNT] armed: at=RunResetUserDataSequenceAsync freshUuid=%@", freshUuid]);
return s_origRunResetSeq ? s_origRunResetSeq(ct, mi) : (KIOUUniTaskRet){0, 0};
}

KIOUUniTaskRet KIOUHookRunDeleteAccountSeq(void *ct, void *mi) {
IPALog([NSString stringWithFormat:
@"[ACCOUNT] RunDeleteAccountSequenceAsync (active=%@)",
@"[ACCOUNT] fire: at=RunDeleteAccountSequenceAsync active=%@",
KIOUActiveAccountUserId() ?: @"(none)"]);
return s_origRunDeleteAccountSeq ? s_origRunDeleteAccountSeq(ct, mi) : (KIOUUniTaskRet){0, 0};
}
Expand All @@ -351,21 +351,21 @@ KIOUUniTaskRet KIOUHookRunDeleteAccountSeq(void *ct, void *mi) {

void KIOUNavigateToTitleScene(void) {
if (g_unityBase == 0) {
IPALog(@"[ACCOUNT] KIOUNavigateToTitleScene: unityBase not yet set");
IPALog(@"[ACCOUNT] skipped: at=KIOUNavigateToTitleScene reason=unityBaseUnset");
return;
}
uintptr_t addr = KIOUHookSiteAddr(KIOU_HOOK_NAME_BACK_TO_TITLE_RUN_ASYNC, g_unityBase);
if (addr == 0) {
IPALog(@"[ACCOUNT] KIOUNavigateToTitleScene: site address unknown");
IPALog(@"[ACCOUNT] skipped: at=KIOUNavigateToTitleScene reason=siteAddrUnknown");
return;
}
BackToTitleRunAsync_t fn = (BackToTitleRunAsync_t)addr;
@try {
(void)fn(NULL, NULL);
IPALog(@"[ACCOUNT] BackToTitleSequence.RunAsync invoked");
IPALog(@"[ACCOUNT] fire: at=BackToTitleSequence.RunAsync");
} @catch (NSException *e) {
IPALog([NSString stringWithFormat:
@"[ACCOUNT] BackToTitleSequence.RunAsync threw: %@", e]);
@"[ACCOUNT] exception: at=BackToTitleSequence.RunAsync error=%@", e]);
}
}

Expand All @@ -378,37 +378,42 @@ void KIOUInstallAccountObserveHook(uintptr_t unityBase) {

s_origAccountExists = (AccountExists_t)
KIOUHookInstall(KIOU_HOOK_NAME_ACCOUNT_EXISTS, (void *)KIOUHookAccountExists, unityBase);
KIOU_HOOK_PUBLISH_SLOT(unityBase, KIOU_HOOK_SLOT_ACCOUNT_EXISTS, KIOUHookAccountExists);
s_origLoginArgsCreate = (LoginArgsCreate_t)
KIOUHookInstall(KIOU_HOOK_NAME_LOGIN_ARGS_CREATE, (void *)KIOUHookLoginArgsCreate, unityBase);
KIOU_HOOK_PUBLISH_SLOT(unityBase, KIOU_HOOK_SLOT_LOGIN_ARGS_CREATE, KIOUHookLoginArgsCreate);
s_origRegisterUserArgsCreate = (RegisterUserArgsCreate_t)
KIOUHookInstall(KIOU_HOOK_NAME_REGISTER_USER_ARGS_CREATE, (void *)KIOUHookRegisterUserArgsCreate, unityBase);
KIOU_HOOK_PUBLISH_SLOT(unityBase, KIOU_HOOK_SLOT_REGISTER_USER_ARGS_CREATE, KIOUHookRegisterUserArgsCreate);
s_origRunLoginSeqMoveNext = (MoveNextVoid_t)
KIOUHookInstall(KIOU_HOOK_NAME_RUN_LOGIN_SEQ_MOVENEXT, (void *)KIOUHookRunLoginSeqMoveNext, unityBase);
KIOU_HOOK_PUBLISH_SLOT(unityBase, KIOU_HOOK_SLOT_RUN_LOGIN_SEQ_MOVENEXT, KIOUHookRunLoginSeqMoveNext);
s_origGetSelfProfileMoveNext = (MoveNextVoid_t)
KIOUHookInstall(KIOU_HOOK_NAME_GET_SELF_PROFILE_MOVENEXT, (void *)KIOUHookGetSelfProfileMoveNext, unityBase);
KIOU_HOOK_PUBLISH_SLOT(unityBase, KIOU_HOOK_SLOT_GET_SELF_PROFILE_MOVENEXT, KIOUHookGetSelfProfileMoveNext);

#if IPA_CHINLAN
(void)KF_RVA_RUN_RESET_USER_DATA_SEQ;
(void)KF_RVA_RUN_DELETE_ACCOUNT_SEQ;
IPALog(@"[ACCOUNT] chinlan: catalog hooks resolved; raw-RVA hooks (Reset/Delete) skipped");
IPALog(@"[ACCOUNT] resolved: variant=chinlan catalogHooks=yes rawRvaHooks=skipped");
#else
// RunReset / RunDelete are JB-only sites (no chinlan cave for them).
{
uintptr_t addr = unityBase + KF_RVA_RUN_RESET_USER_DATA_SEQ;
uintptr_t addr = KIOUHookSiteAddr(
KIOU_HOOK_NAME_RUN_RESET_USER_DATA_SEQ, unityBase);
void *orig = NULL;
MSHookFunction((void *)addr, (void *)KIOUHookRunResetUserDataSeq, &orig);
s_origRunResetSeq = (RunResetSeq_t)orig;
IPALog([NSString stringWithFormat:
@"[ACCOUNT] hooked RunResetUserDataSeq @0x%lx", (unsigned long)addr]);
@"[ACCOUNT] hooked: target=RunResetUserDataSeq addr=0x%lx", (unsigned long)addr]);
}
{
uintptr_t addr = unityBase + KF_RVA_RUN_DELETE_ACCOUNT_SEQ;
uintptr_t addr = KIOUHookSiteAddr(
KIOU_HOOK_NAME_RUN_DELETE_ACCOUNT_SEQ, unityBase);
void *orig = NULL;
MSHookFunction((void *)addr, (void *)KIOUHookRunDeleteAccountSeq, &orig);
s_origRunDeleteAccountSeq = (RunResetSeq_t)orig;
IPALog([NSString stringWithFormat:
@"[ACCOUNT] hooked RunDeleteAccountSeq @0x%lx", (unsigned long)addr]);
@"[ACCOUNT] hooked: target=RunDeleteAccountSeq addr=0x%lx", (unsigned long)addr]);
}
IPALog(@"[ACCOUNT] hooks installed");
IPALog(@"[ACCOUNT] installed:");
#endif
}
3 changes: 2 additions & 1 deletion Hook/AfkDisable.m
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ void KIOUEditorInstallAfkDisableHook(uintptr_t unityBase) {
s_origGO_IsAfkEnabled = (GameOrchestratorIsAfkEnabled_t)KIOUHookInstall(
KIOU_HOOK_NAME_GAME_ORCHESTRATOR_IS_AFK,
(void *)hook_GO_IsAfkEnabled, unityBase);
KIOU_HOOK_PUBLISH_SLOT(unityBase, KIOU_HOOK_SLOT_GAME_ORCHESTRATOR_IS_AFK, hook_GO_IsAfkEnabled);
IPALog([NSString stringWithFormat:
@"[AFK] installed: orig=%p (toggled by KIOU_FEATURE_DISABLE_AFK)",
@"[AFK] installed: orig=%p gate=KIOU_FEATURE_DISABLE_AFK",
(void *)s_origGO_IsAfkEnabled]);
}
Loading
Loading