This commit is contained in:
MoonLeeeaf
2024-06-07 12:33:59 +08:00
commit 1a169f2124
25 changed files with 1060 additions and 0 deletions

1
app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

49
app/build.gradle Normal file
View File

@@ -0,0 +1,49 @@
plugins {
id 'com.android.application'
}
android {
namespace 'io.github.moonleeeaf.fuckmaonemo'
compileSdk 34
defaultConfig {
applicationId "io.github.moonleeeaf.fuckmaonemo"
minSdk 21
targetSdk 33
versionCode 10100
versionName "1.0.1"
vectorDrawables {
useSupportLibrary true
}
}
packagingOptions {
jniLibs {
useLegacyPackaging true
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
buildFeatures {
viewBinding false
}
}
dependencies {
compileOnly 'de.robv.android.xposed:api:82'
}

21
app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:roundIcon="@drawable/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="false"
android:extractNativeLibs="false"
android:theme="@android:style/Theme.DeviceDefault">
<activity
android:name=".ConfigActivity"
android:exported="true">
<intent-filter>
<category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" />
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="@string/description" />
<meta-data
android:name="xposedminversion"
android:value="53" />
<meta-data
android:name="xposedscope"
android:resource="@array/xposedscope" />
<meta-data
android:name="xposedsharedprefs"
android:value="true" />
</application>
</manifest>

View File

@@ -0,0 +1 @@
io.github.moonleeeaf.fuckmaonemo.Hook

View File

@@ -0,0 +1,36 @@
package io.github.moonleeeaf.fuckmaonemo;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Toolbar;
public class ConfigActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
getPreferenceManager().setSharedPreferencesName("config");
// 理应会被 LSPosed Hook 的,不过兼容性未知
getPreferenceManager().setSharedPreferencesMode(MODE_WORLD_READABLE);
addPreferencesFromResource(R.xml.config);
findPreference("about").setOnPreferenceClickListener((p) -> {
new AlertDialog.Builder(this)
.setTitle("关于")
.setMessage("FuckMaoNemo 是一个对 编程猫Nemo/点个猫 一些不合理设计的强制补充以及对其部分功能缺失进行弥补,并提供一些其他十分实用的功能,为人民服务,帮助大家(。・ω・。)\n作者满月叶")
.show();
return false;
});
}
}

View File

@@ -0,0 +1,124 @@
package io.github.moonleeeaf.fuckmaonemo;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.widget.Toast;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XSharedPreferences;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import java.lang.reflect.Method;
public class Hook implements IXposedHookLoadPackage {
private static boolean isHooked = false;
private XSharedPreferences xsp;
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam param) throws Throwable {
if ("com.codemao.nemo".equals(param.packageName)) {
XposedBridge.log("[FuckMaoNemo] 开始注入...");
// 感谢 安宁 提供取加固程序的 ClassLoader 的代码
XposedBridge.hookAllMethods(
XposedHelpers.findClass("android.app.ActivityThread", param.classLoader),
"performLaunchActivity",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam mParam) throws Throwable {
super.afterHookedMethod(mParam);
Object initApp = XposedHelpers.getObjectField(mParam.thisObject, "mInitialApplication");
hook(param, initApp.getClass().getClassLoader());
}
}
);
}
}
public static Method getMethod(Class clazz, String name, Class... args) throws NoSuchMethodException {
return clazz.getDeclaredMethod(name, args);
}
public Application getApplication() throws ClassNotFoundException {
return (Application) XposedHelpers.callStaticMethod(Class.forName("android.app.ActivityThread"), "currentApplication");
}
public void hook(XC_LoadPackage.LoadPackageParam param, ClassLoader classLoader) throws Exception {
if (isHooked) return;
else isHooked = true;
int nohengheng = 0;
xsp = new XSharedPreferences("io.github.moonleeeaf.fuckmaonemo", "config");
XposedBridge.log("[FuckMaoNemo] 注入中...");
// 拦截40x码
if (xsp.getBoolean("fuck_40x", false)) {
XposedBridge.log("[FuckMaoNemo] Hook_拦截40x码");
XposedBridge.hookMethod(
getMethod(
XposedHelpers.findClass("com.codemao.nemo.retrofit.response.CommonSubcriber", classLoader),
"onNext",
XposedHelpers.findClass("retrofit2.Response", classLoader)
),
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam mP) throws Throwable {
Object res = mP.args[0];
int code = (int) XposedHelpers.callMethod(res, "code");
if(code >= 400 && code <500) {
Object rawRes = XposedHelpers.getObjectField(res, "rawResponse");
XposedHelpers.setIntField(rawRes, "code", 200);
XposedBridge.log("[FuckMaoNemo] 拦截响应 40x 码");
String t = "";
switch (code){
case 401:
t = "已拦截异常登出";
break;
case 422:
t = "已拦截封号页面替换资料卡";
break;
case 405:
t = "评论接口被禁止";
break;
default:
t = "未知拦截,响应码为 " + code;
}
Toast.makeText(getApplication(), "[FuckMaoNemo] " + t, Toast.LENGTH_SHORT).show();
}
}
}
);
nohengheng++;
}
// 绕过防沉迷
if (xsp.getBoolean("fuck_fcm", false)) {
XposedBridge.log("[FuckMaoNemo] Hook_绕过防沉迷");
XposedBridge.hookMethod(
getMethod(
XposedHelpers.findClass("com.codemao.nemo.activity.WorkDetailActivity", classLoader),
"checkAntiAddictionState",
null
),
new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam arg0) throws Throwable {
XposedBridge.log("[FuckMaoNemo] 拦截防沉迷方法调用");
return null;
}
}
);
nohengheng++;
}
XposedBridge.log("[FuckMaoNemo] 执行完毕");
Toast.makeText(getApplication(), "[FuckMaoNemo] 加载成功 (≧▽≦)\n" + nohengheng + " 个功能已加载", Toast.LENGTH_LONG).show();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 KiB

View File

@@ -0,0 +1,6 @@
<resources>
<string-array name="xposedscope" >
<!-- 模块的作用域应用的包名 -->
<item>com.codemao.nemo</item>
</string-array>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">FuckMaoNemo</string>
<string name="description">Nemo 辅助模块\n编程猫 @满月叶\nGitHub @MoonLeeeaf</string>
</resources>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@@ -0,0 +1,38 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="关于">
<Preference
android:key="about"
android:title="关于"
android:summary="关于 FuckMaoNemo" />
<Preference
android:title="作者"
android:summary="编程猫 @满月叶\nGitHub @MoonLeeeaf" />
</PreferenceCategory>
<PreferenceCategory android:title="网络">
<SwitchPreference
android:key="fuck_40x"
android:title="阻止服务端响应 40x 码"
android:summary="启用此开关后,将可以:\n拦截 401 码强制登出账号\n强制浏览已封禁账号的资料卡\n评论接口检测\n其他作用" />
</PreferenceCategory>
<PreferenceCategory android:title="作品">
<SwitchPreference
android:key="fuck_fcm"
android:title="绕过防沉迷"
android:summary="滞空防沉迷检测方法的调用实现无伤速通破解防沉迷,无需 Player 链接" />
<SwitchPreference
android:key="force_rework"
android:title="强制显示再创作"
android:enabled="false"
android:summary="使作品的 再创作 按钮永远显示即使作品并未设置为开放源代码未制作QWQ" />
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>