diff --git a/app/src/main/java/cc/ioctl/hook/msg/RevokeMsgHook.java b/app/src/main/java/cc/ioctl/hook/msg/RevokeMsgHook.java index 22ec52ae9d..1cd748373b 100644 --- a/app/src/main/java/cc/ioctl/hook/msg/RevokeMsgHook.java +++ b/app/src/main/java/cc/ioctl/hook/msg/RevokeMsgHook.java @@ -88,6 +88,8 @@ * 2020/04/08 Tue.23:21 Use RevokeMsgInfoImpl for ease, wanna cry *

* 2023-06-16 Fri.12:40 Initial support for NT kernel. + *

+ * 2023-07-12 Mon.23:12 Basic support for NT kernel. */ @FunctionHookEntry @UiItemAgentEntry @@ -179,37 +181,36 @@ public void setEnabled(boolean value) { @Override public boolean initOnce() throws Exception { - if (QAppUtils.isQQnt()) { - nativeInitNtKernelRecallMsgHook(); - } else { - Method revokeMsg = null; - for (Method m : _QQMessageFacade().getDeclaredMethods()) { - if (m.getReturnType().equals(void.class)) { - Class[] argt = m.getParameterTypes(); - if (argt.length == 2 && argt[0].equals(ArrayList.class) && argt[1] - .equals(boolean.class)) { - revokeMsg = m; - break; - } + nativeInitNtKernelRecallMsgHook(); + // The method is still there, even on NT. + // I decided to hook them as long as they are there. + // There should only be one method with such signature. + Method revokeMsg = null; + for (Method m : _QQMessageFacade().getDeclaredMethods()) { + if (m.getReturnType().equals(void.class)) { + Class[] argt = m.getParameterTypes(); + if (argt.length == 2 && argt[0].equals(ArrayList.class) && argt[1].equals(boolean.class)) { + revokeMsg = m; + break; } } - HookUtils.hookBeforeIfEnabled(this, revokeMsg, -10086, param -> { - mQQMsgFacade = param.thisObject; - ArrayList list = (ArrayList) param.args[0]; - param.setResult(null); - if (list == null || list.isEmpty()) { - return; - } - for (Object revokeMsgInfo : list) { - try { - onRevokeMsgLegacy(revokeMsgInfo); - } catch (Exception | LinkageError | AssertionError t) { - traceError(t); - } - } - list.clear(); - }); } + HookUtils.hookBeforeIfEnabled(this, revokeMsg, -10086, param -> { + mQQMsgFacade = param.thisObject; + ArrayList list = (ArrayList) param.args[0]; + param.setResult(null); + if (list == null || list.isEmpty()) { + return; + } + for (Object revokeMsgInfo : list) { + try { + onRevokeMsgLegacy(revokeMsgInfo); + } catch (Exception | LinkageError | AssertionError t) { + traceError(t); + } + } + list.clear(); + }); return true; } @@ -248,6 +249,7 @@ private void onRevokeMsgLegacy(Object revokeMsgInfo) throws Exception { aioSessionUin = info.friendUin; } else { // XXX: maybe confession chat? 3000: temp C2C chat? guild? + // XXX: ??? 1000 ??? throw new IllegalStateException("onRevokeMsg, istroop=" + istroop); } @@ -374,10 +376,22 @@ private void onRevokeMsgLegacy(Object revokeMsgInfo) throws Exception { * We are not allowed to throw any exception in this method. */ @Keep - private static void handleRecallSysMsgFromNtKernel(int chatType, String peerUid, String recallOpUid, String msgAuthorUid, + private static void handleRecallSysMsgFromNtKernel(int chatType, String fromUid, String recallOpUid, String msgAuthorUid, String toUid, long random64, long timeSeconds, long msgUid, long msgSeq, int msgClientSeq) { SyncUtils.async(() -> { try { + String selfUid = RelationNTUinAndUidApi.getUidFromUin(AppRuntimeHelper.getAccount()); + if (TextUtils.isEmpty(selfUid)) { + Log.e("onRecallSysMsgForNT fatal: selfUid is empty"); + return; + } + String peerUid; + if (selfUid.equals(fromUid)) { + // msg is revoked by myself + peerUid = toUid; + } else { + peerUid = fromUid; + } RevokeMsgHook.INSTANCE.onRecallSysMsgForNT(chatType, peerUid, recallOpUid, msgAuthorUid, toUid, random64, timeSeconds, msgUid, msgSeq, msgClientSeq); } catch (Exception | LinkageError | AssertionError e) { @@ -408,10 +422,6 @@ private void onRecallSysMsgForNT(int chatType, String peerUid, String recallOpUi Log.e("onRecallSysMsgForNT fatal: msgAuthorUid is empty"); return; } - if (selfUid.equals(recallOpUid)) { - // ignore msg revoked by self - return; - } if ((chatType == 1 && !Objects.equals(selfUid, toUid)) || (chatType == 2 && !Objects.equals(peerUid, toUid))) { Log.w("!!! onRecallSysMsgForNT potential bug: chatType=" + chatType + ", peerUid=" + peerUid + ", toUid=" + toUid + ", selfUid=" + selfUid); } @@ -428,14 +438,14 @@ private void onRecallSysMsgForNT(int chatType, String peerUid, String recallOpUi if (queryResult == 0 && msgList != null && !msgList.isEmpty()) { msgObject = msgList.get(0); } else if (queryResult == 0) { - Log.d("onRecallSysMsgForNT: msg not found, msgUid=" + msgUid); + Log.d("onRecallSysMsgForNT: msg not found, msgSeq=" + msgSeq); } else { Log.e("onRecallSysMsgForNT error: getMsgsByMsgId failed, result=" + queryResult + ", errMsg=" + errMsg); } NtGrayTipHelper.NtGrayTipJsonBuilder builder = new NtGrayTipHelper.NtGrayTipJsonBuilder(); String summary; if (chatType == ChatTypeConstants.C2C) { - String revokerPron = selfUid.equals(peerUid) ? "你" : "对方"; + String revokerPron = selfUid.equals(recallOpUid) ? "你" : "对方"; if (msgObject != null && !(msgObject.getMsgType() == 5 && msgObject.getSubMsgType() == 4)) { builder.appendText(revokerPron + "尝试撤回"); builder.append(new NtGrayTipHelper.NtGrayTipJsonBuilder.MsgRefItem("一条消息", msgSeq)); @@ -474,14 +484,26 @@ private void onRecallSysMsgForNT(int chatType, String peerUid, String recallOpUi } } else { // we don't have the original message + if (selfUid.equals(recallOpUid) && (msgObject.getMsgType() == 5 && msgObject.getSubMsgType() == 4)) { + // the message is recalled by self, and the gray tip is already there + // we don't need to do anything here, otherwise we will have duplicated gray tip. + // Control Flow Termination. + return; + } String msgAuthorName = ContactUtils.getDisplayNameForUid(msgAuthorUid, peerUid); String msgAuthorUin = RelationNTUinAndUidApi.getUinFromUid(msgAuthorUid); // msgAuthorUin may be empty, in the case when in a group chat, NT kernel are not so familiar with the one - builder.append(new NtGrayTipHelper.NtGrayTipJsonBuilder.UserItem(String.valueOf(operatorUin), recallOpUid, operatorName)); - builder.appendText("撤回了"); - builder.append(new NtGrayTipHelper.NtGrayTipJsonBuilder.UserItem(String.valueOf(msgAuthorUin), msgAuthorUid, msgAuthorName)); - builder.appendText("的一条消息(没收到) [seq=" + msgSeq + "]"); - summary = operatorName + "撤回了" + msgAuthorName + "的一条消息(没收到)"; + if (recallOpUid.equals(msgAuthorUin)) { + builder.append(new NtGrayTipHelper.NtGrayTipJsonBuilder.UserItem(String.valueOf(operatorUin), recallOpUid, operatorName)); + builder.appendText("撤回了一条消息(没收到) [seq=" + msgSeq + "]"); + summary = operatorName + "撤回了一条消息(没收到)"; + } else { + builder.append(new NtGrayTipHelper.NtGrayTipJsonBuilder.UserItem(String.valueOf(operatorUin), recallOpUid, operatorName)); + builder.appendText("撤回了"); + builder.append(new NtGrayTipHelper.NtGrayTipJsonBuilder.UserItem(String.valueOf(msgAuthorUin), msgAuthorUid, msgAuthorName)); + builder.appendText("的一条消息(没收到) [seq=" + msgSeq + "]"); + summary = operatorName + "撤回了" + msgAuthorName + "的一条消息(没收到)"; + } } } else { throw new AssertionError("onRecallSysMsgForNT chatType=" + chatType);