返回列表 发新帖

【方法论+成品】K歌SSS评分破解 - 教你"找对地方再下手"

[复制链接]

62

主题

653

回帖

2250

积分

大学生

Rank: 5Rank: 5

金币
1111
好评
23
信誉
40

MT论坛最佳新人考神

QQ
发表于 2026-5-16 13:11:39 | 显示全部楼层 | 阅读模式  来自 比利时
本帖最后由 最强AI选手 于 2026-5-16 23:09 编辑


上篇VIP教程发完发现一个问题:
绝大多数教程都是"搜这个字符串→改成1→完成", 看的人抄一遍
新版本类名一变又得重新求人

这次先讲思路 后给成品
想抄的拉到第七章
想学的从头看


============================================
一、99%的人都走过的弯路
============================================
我自己就走了

刚开始做K歌评分破解, 思路是:
1. 反编译看到 MultiScore 类有5个分数字段
2. hook它, 把字段都设成100
3. 录歌看效果 → 没生效
4. 再hook MultiScoreDetail / RecordingCalorieScoreData / kw2.a ...
5. 全部hook完依然不生效

为什么?

这些都是"下游"数据类, 真正的分数在更深的地方算出来后才"流"到这些类
hook下游 = 在水管末端拦水, 但水龙头一直在出新水, 拦不过来


============================================
二、真正要思考的问题
============================================
不是"在哪个类里把分数改成100"
而是"分数最初是在哪里产生的"

K歌评分的数据流向(任何评分类APP都类似):

  麦克风采集音频 (PCM)
       ↓
  JNI传给底层so做声学分析
       ↓
  so算出原始分数 → 存到一个Java对象里 (★这里是源头)
       ↓
  SDK暴露 getter 给业务层调
       ↓
  业务层包装成各种业务数据类
       ↓
  UI层从业务数据类读出来显示

99%的人在最后两层做hook, 我也是
正确做法在第3层 — SDK暴露getter的地方做hook


============================================
三、怎么找到"源头"
============================================
方法1: 看包名结构(最快)

K歌包名里有这俩:
  com.tencent.karaoke.audiobasesdk.scorer.*  ← 音频SDK评分包
  com.tencent.karaoke.module.songedit.*      ← 业务层

看到 audiobasesdk 这种命名, 一眼就知道是底层SDK, 优先扒它

方法2: 看so列表

解包APK看 lib/arm64-v8a/ 下哪些so, 关注:
  libxxxScore.so / libxxxScorer.so
  libxxxsing.so / libxxxKtv.so

这些native库往上一层就是Java SDK封装层

方法3: 看日志关键字

我抓到一条日志:
  DefaultScoreImpl$setScoreResultCallback ... updateScoreResult

DefaultScoreImpl 这名字本身就告诉你"我是默认评分实现"
直接锁定这个类作为入口


============================================
四、找到核心类后看什么
============================================
com.tencent.karaoke.audiobasesdk.scorer.DefaultScoreImpl 字段方法:

字段:
  result: ScoreResult         ← ★最终结果对象
  validScoreArray: [I         ← ★有效句子分数数组

方法:
  getScoreResult()            ← getter (业务层从这取)
  getValidScoreArray()        ← getter
  setScoreResultCallback(...) ← 注册回调
  processWithBuffer(...)      ← 实时处理音频

重点是 result 字段 → 它的类型 ScoreResult 才是真宝藏:

ScoreResult 暴露的所有 getter:
  acfTotalScore() / pYinTotalScore()           ← 总分(2套算法)
  acfSentenceScores() / pYinSentenceScores()   ← 每句分数数组
  rhythmScore() / stableScore() / longtoneScore() / skillScore()  ← 五维分
  rhythmScoreArray() / stableScoreArray() / longtoneScoreArray()  ← 每句五维
  graceNum() / vibNum() / porNum()             ← 装饰音/颤音
  rap()                                         ← 说唱判断

一切上层显示的分数都从这取


============================================
五、Hook策略
============================================
不要hook业务层 不要hook UI层
就hook ScoreResult 这个数据类的所有getter

策略:
- 所有 ()I 方法:
  - 名字含 Total → 返回 句子数 × 100
  - 其他单值分数 → 直接返回 100
- 所有 ()[I 方法:
  - 拿到原数组, 遍历改成全100
  - 不要新建数组, 不要改长度


============================================
六、关键坑
============================================
坑1: 别 hook 句子数
  getValidSentenceNum() 这种别动
  它是真实歌词句数, 改了UI显示会乱

坑2: byte[] 数据别动
  getNewScores()[B 这种绕开
  二进制结构, 字段位置hardcode偏移量, 改一个字节闪退

坑3: 别贪心改 Total
  总分 = 句子数 × 单句分
  返回 9999 但句子数是15, 后端比对会算异常
  正确: 返回 句子数 × 100

坑4: 数组改值 vs 改引用
  错误写法:
    int[] arr = (int[]) param.getResult();
    arr = new int[]{100, 100, ...};   ← 错!只改了局部变量

  正确写法:
    int[] arr = (int[]) param.getResult();
    for (int i = 0; i < arr.length; i++) arr = 100;   ← 对


============================================
七、成品代码 (想直接抄的看这里)
============================================
新建 ScoreHook.java 放到模块工程:

package com.kmod.karaoke;

import java.lang.reflect.Method;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class ScoreHook {
    public static final int SENTENCE_SCORE = 100;

    public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
        ClassLoader cl = lpparam.classLoader;
        try { hookScoreResult(cl); } catch (Throwable t) { XposedBridge.log("[ScoreHook] " + t); }
        try { hookDefaultScoreImpl(cl); } catch (Throwable t) { XposedBridge.log("[ScoreHook] " + t); }
    }

    private static void hookScoreResult(ClassLoader cl) {
        Class<?> clazz = XposedHelpers.findClass(
                "com.tencent.karaoke.audiobasesdk.scorer.ScoreResult", cl);
        for (Method m : clazz.getDeclaredMethods()) {
            if (m.isSynthetic()) continue;
            if (m.getParameterTypes().length != 0) continue;
            Class<?> ret = m.getReturnType();
            final String name = m.getName();
            if (ret == int.class) {
                XposedBridge.hookMethod(m, new XC_MethodReplacement() {
                    @Override
                    protected Object replaceHookedMethod(MethodHookParam param) {
                        if (name.contains("Total")) {
                            int sentences = getValidSentenceCount(param.thisObject);
                            return sentences * SENTENCE_SCORE;
                        }
                        return SENTENCE_SCORE;
                    }
                });
            } else if (ret == int[].class) {
                XposedBridge.hookMethod(m, new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) {
                        int[] arr = (int[]) param.getResult();
                        if (arr != null) {
                            for (int i = 0; i < arr.length; i++) arr = SENTENCE_SCORE;
                        }
                    }
                });
            }
        }
    }

    private static int getValidSentenceCount(Object scoreResult) {
        if (scoreResult == null) return 1;
        try {
            Method m = scoreResult.getClass().getDeclaredMethod("acfSentenceScores");
            m.setAccessible(true);
            Object arr = m.invoke(scoreResult);
            if (arr instanceof int[]) {
                int len = ((int[]) arr).length;
                if (len > 0) return len;
            }
        } catch (Throwable ignore) {}
        return 1;
    }

    private static void hookDefaultScoreImpl(ClassLoader cl) {
        Class<?> clazz = XposedHelpers.findClass(
                "com.tencent.karaoke.audiobasesdk.scorer.DefaultScoreImpl", cl);
        for (Method m : clazz.getDeclaredMethods()) {
            if (m.isSynthetic()) continue;
            if (m.getParameterTypes().length != 0) continue;
            if (m.getReturnType() != int[].class) continue;
            XposedBridge.hookMethod(m, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) {
                    int[] arr = (int[]) param.getResult();
                    if (arr != null) {
                        for (int i = 0; i < arr.length; i++) arr = SENTENCE_SCORE;
                    }
                }
            });
        }
    }
}

在 MainHook.handleLoadPackage 里加一行调用:

  try {
      ScoreHook.install(lpparam);
  } catch (Throwable t) {
      XposedBridge.log("ScoreHook failed: " + t);
  }


============================================
八、效果
============================================
旧版hook(业务层): C级 24分 / 170分
新版hook(SDK层): SSS级 4000+分 (40句歌词)
五维满分, 最大连击=句数


============================================
九、举一反三
============================================
这套思路适用于所有有"评分"概念的APP:
- 音乐识别评分
- 健身APP的卡路里/动作评分
- 游戏的得分

通用步骤:
1. 看包名 找带 sdk / engine / core 的包
2. 看类名 找 Default*Impl / *Result / *Bean
3. 看字段 找 result / data 这种"成品"字段
4. 看方法签名 优先 ()I / ()[I / ()F 这种纯getter

不要被那些"搜某个字符串改1"的教程牵着走
先理解APP数据从哪流到哪 再决定在哪打断


============================================
结尾
============================================
代码免费抄
但真心建议照思路自己重写一遍
下次类名一变, 抄的人傻眼, 学的人五分钟搞定



回复

使用道具 举报

62

主题

653

回帖

2250

积分

大学生

Rank: 5Rank: 5

金币
1111
好评
23
信誉
40

MT论坛最佳新人考神

QQ
发表于 2026-5-16 19:20:13 来自手机  | 显示全部楼层  来自 浙江
回复

使用道具 举报

95

主题

8172

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
287
好评
28
信誉
108

考神MT论坛新人MT论坛帅哥MT论坛最佳新人MT论坛活跃会员

发表于 2026-5-16 21:02:39 | 显示全部楼层  来自 湖北
感谢分享!
回复

使用道具 举报

3

主题

178

回帖

510

积分

初中生

Rank: 3Rank: 3

金币
331
好评
0
信誉
100
发表于 2026-8-10 22:33:13 来自手机  | 显示全部楼层  来自 广西
高手!
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表