Skip to content

Commit 9ee70b5

Browse files
committed
feat: debugging patch
1 parent 9278f24 commit 9ee70b5

2 files changed

Lines changed: 54 additions & 269 deletions

File tree

extensions/extension/src/main/java/app/revanced/extension/customfilters/TintFieldHook.java

Lines changed: 40 additions & 255 deletions
Original file line numberDiff line numberDiff line change
@@ -10,290 +10,75 @@
1010
import java.lang.reflect.Method;
1111
import java.lang.reflect.Field;
1212

13-
/**
14-
* Late-injection helper. Called from smali injection.
15-
*
16-
* This will:
17-
* - schedule retries until a) we get a usable context/module or b) timeout
18-
* - when ready show a toast and attempt to add the tint field via reflection
19-
*/
2013
public class TintFieldHook {
2114
private static final String TAG = "CustomFilterHook";
22-
private static final int INITIAL_DELAY_MS = 200; // first check delay
23-
private static final int RETRY_INTERVAL_MS = 500; // interval between retries
24-
private static final int MAX_RETRIES = 40; // ~20 seconds max
2515

26-
public static void installTintField(final Object toolTable) {
16+
// Simple ping to check injection
17+
public static void ping(Object toolTable) {
2718
try {
28-
Log.i(TAG, "installTintField() invoked, scheduling readiness checks...");
29-
scheduleAttempt(toolTable, 0);
19+
Log.i(TAG, "ping() called. toolTable class: " + (toolTable != null ? toolTable.getClass().getName() : "null"));
3020
} catch (Throwable t) {
31-
// Must never let this bubble out into the app process
32-
Log.e(TAG, "installTintField top-level failure", t);
21+
Log.e(TAG, "ping() error", t);
3322
}
3423
}
3524

36-
private static void scheduleAttempt(final Object toolTable, final int attempt) {
25+
// schedule the real install a little later on the main thread
26+
public static void scheduleInstall(final Object toolTable) {
3727
try {
38-
// use main looper so any UI calls are safe
39-
Handler mainHandler = new Handler(Looper.getMainLooper());
40-
int delay = (attempt == 0) ? INITIAL_DELAY_MS : RETRY_INTERVAL_MS;
41-
42-
mainHandler.postDelayed(() -> {
43-
try {
44-
if (toolTable == null) {
45-
Log.w(TAG, "toolTable is null, aborting attempts.");
46-
return;
47-
}
48-
49-
// Try to obtain a context (via getContext() on the Module or toolTable)
50-
Context context = tryGetContext(toolTable);
51-
boolean hasContext = context != null;
52-
53-
Log.i(TAG, "attempt " + attempt + " - context available: " + hasContext);
54-
55-
// Try to get module or other indicators that initialization ran
56-
Object module = tryGetModule(toolTable);
57-
boolean hasModule = module != null;
58-
Log.i(TAG, "attempt " + attempt + " - module available: " + hasModule +
59-
(module != null ? (" (" + module.getClass().getName() + ")") : ""));
60-
61-
// If we have a context -> show a toast so you can see it's working
62-
if (context != null) {
63-
final Context ctx = context;
64-
// show toast on main thread
65-
Toast.makeText(ctx, "✅ TintFieldHook triggered (late)!", Toast.LENGTH_SHORT).show();
66-
}
67-
68-
// If we have module and context, attempt to install the field (safe reflect)
69-
if (hasModule && hasContext) {
70-
boolean installed = tryInstallField(toolTable, module, context);
71-
if (installed) {
72-
Log.i(TAG, "Tint field installed successfully; stopping retries.");
73-
return; // success -> stop retrying
74-
} else {
75-
Log.i(TAG, "tryInstallField returned false (requirements not met yet).");
76-
}
77-
}
78-
79-
// Not ready yet -> retry until MAX_RETRIES
80-
if (attempt < MAX_RETRIES) {
81-
scheduleAttempt(toolTable, attempt + 1);
82-
} else {
83-
Log.w(TAG, "TintFieldHook: reached max retries, giving up.");
28+
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
29+
@Override
30+
public void run() {
31+
try {
32+
installTintField(toolTable);
33+
} catch (Throwable t) {
34+
Log.e(TAG, "scheduled install failed", t);
8435
}
85-
86-
} catch (Throwable inner) {
87-
// swallow and continue retrying to avoid crashing the host app
88-
Log.e(TAG, "Error during scheduled attempt", inner);
89-
if (attempt < MAX_RETRIES) scheduleAttempt(toolTable, attempt + 1);
90-
}
91-
}, delay);
92-
} catch (Throwable t) {
93-
Log.e(TAG, "Failed scheduling attempt", t);
94-
}
95-
}
96-
97-
/**
98-
* Try to call toolTable.getModule() or fallback to other getters.
99-
*/
100-
private static Object tryGetModule(Object toolTable) {
101-
try {
102-
// Many ToolTable classes have getModule() (in your earlier decompiled code it did)
103-
Method getModule = null;
104-
try {
105-
getModule = toolTable.getClass().getMethod("getModule");
106-
} catch (NoSuchMethodException ignored) {
107-
}
108-
if (getModule != null) {
109-
Object module = getModule.invoke(toolTable);
110-
if (module != null) return module;
111-
}
112-
113-
// Some implementations may store a 'module' field; check reflectively
114-
try {
115-
Field moduleField = toolTable.getClass().getDeclaredField("mModule");
116-
moduleField.setAccessible(true);
117-
Object module = moduleField.get(toolTable);
118-
if (module != null) return module;
119-
} catch (NoSuchFieldException ignored) {
120-
}
121-
122-
} catch (Throwable t) {
123-
Log.w(TAG, "tryGetModule failed", t);
124-
}
125-
return null;
126-
}
127-
128-
/**
129-
* Try to obtain a Context. Try several strategies safely.
130-
*/
131-
private static Context tryGetContext(Object toolTable) {
132-
try {
133-
// 1) direct getContext() on toolTable
134-
try {
135-
Method getContext = toolTable.getClass().getMethod("getContext");
136-
Object ctx = getContext.invoke(toolTable);
137-
if (ctx instanceof Context) return (Context) ctx;
138-
} catch (NoSuchMethodException ignored) {
139-
}
140-
141-
// 2) via module.getContext()
142-
Object module = tryGetModule(toolTable);
143-
if (module != null) {
144-
try {
145-
Method getContext = module.getClass().getMethod("getContext");
146-
Object ctx = getContext.invoke(module);
147-
if (ctx instanceof Context) return (Context) ctx;
148-
} catch (NoSuchMethodException ignored) {
14936
}
150-
}
151-
152-
// 3) try a common field lookup (last resort)
153-
try {
154-
Field ctxField = toolTable.getClass().getDeclaredField("mContext");
155-
ctxField.setAccessible(true);
156-
Object ctx = ctxField.get(toolTable);
157-
if (ctx instanceof Context) return (Context) ctx;
158-
} catch (NoSuchFieldException ignored) {
159-
}
160-
37+
}, 200); // 200 ms delay
16138
} catch (Throwable t) {
162-
Log.w(TAG, "tryGetContext failed", t);
39+
Log.e(TAG, "scheduleInstall failed", t);
16340
}
164-
return null;
16541
}
16642

167-
/**
168-
* Attempt to create & register the tint field via reflection.
169-
* Returns true on success (field created and registered), false if not enough requirements yet.
170-
*
171-
* This method purposely tolerates missing constructors/methods and will NOT throw errors up.
172-
*/
173-
private static boolean tryInstallField(Object toolTable, Object module, Context context) {
43+
// do minimal, defensive reflection/UI code here
44+
public static void installTintField(Object table) {
17445
try {
175-
// We want to construct a LabelColorInputIncrementField similar to the app's code:
176-
// new LabelColorInputIncrementField(animationScreen, localizedLabel, "0.00", 4, 0.0f, 1.0f, true);
177-
// Then call registerWidget(table, 107) and setHighFidelity(true), setValue(Color.WHITE) etc.
178-
179-
ClassLoader cl = toolTable.getClass().getClassLoader();
180-
181-
// Load classes reflectively; if any missing, bail out gracefully
182-
Class<?> labelColorFieldClass;
183-
try {
184-
labelColorFieldClass = Class.forName("org.fortheloss.framework.LabelColorInputIncrementField", false, cl);
185-
} catch (Throwable e) {
186-
Log.i(TAG, "LabelColorInputIncrementField class not present yet.");
187-
return false; // not ready to create field
188-
}
189-
190-
// Need animation screen (module.getContext())
191-
Object animationScreen = null;
192-
try {
193-
Method getContext = module.getClass().getMethod("getContext");
194-
animationScreen = getContext.invoke(module);
195-
} catch (NoSuchMethodException ignored) {
196-
}
197-
if (animationScreen == null) {
198-
Log.i(TAG, "animationScreen not available yet.");
199-
return false;
200-
}
201-
202-
// Localization: App.localize("tint")
203-
String tintLabel = "tint";
46+
// defensive: attempt to find context safely
47+
Object contextObj = null;
20448
try {
205-
Class<?> appClass = Class.forName("org.fortheloss.sticknodes.App", false, cl);
206-
Method localize = appClass.getMethod("localize", String.class);
207-
Object res = localize.invoke(null, "tint");
208-
if (res instanceof String) tintLabel = (String) res;
209-
} catch (Throwable t) {
210-
Log.w(TAG, "Could not localize 'tint', using fallback", t);
49+
// In a lot of these classes getModule().getContext() is the path; adapt as needed
50+
java.lang.reflect.Method getModule = table.getClass().getMethod("getModule");
51+
Object module = getModule.invoke(table);
52+
java.lang.reflect.Method getContext = module.getClass().getMethod("getContext");
53+
contextObj = getContext.invoke(module);
54+
} catch (Throwable inner) {
55+
Log.w(TAG, "couldn't get context via getModule/getContext", inner);
21156
}
21257

213-
// Find the constructor signature:
214-
// <init>(AnimationScreen, String, String, int, float, float, boolean)
215-
Constructor<?> ctor = null;
216-
for (Constructor<?> c : labelColorFieldClass.getDeclaredConstructors()) {
217-
Class<?>[] params = c.getParameterTypes();
218-
if (params.length >= 7) { // loose matching (some builds may differ)
219-
ctor = c;
220-
break;
221-
}
222-
}
223-
if (ctor == null) {
224-
Log.i(TAG, "No suitable constructor found for LabelColorInputIncrementField yet.");
225-
return false;
226-
}
227-
228-
ctor.setAccessible(true);
229-
230-
// Create instance (safely)
231-
Object fieldInstance;
232-
try {
233-
// fallback param values (match the game's constructor as best as possible)
234-
Object[] args = new Object[] { animationScreen, tintLabel, "0.00", Integer.valueOf(4),
235-
Float.valueOf(0.0f), Float.valueOf(1.0f), Boolean.TRUE };
236-
fieldInstance = ctor.newInstance(args);
237-
} catch (Throwable t) {
238-
Log.w(TAG, "Constructor invocation failed", t);
239-
return false;
240-
}
241-
242-
// registerWidget(table, id)
243-
try {
244-
Method registerWidget = toolTable.getClass().getMethod("registerWidget", Object.class, int.class);
245-
registerWidget.invoke(toolTable, fieldInstance, Integer.valueOf(107));
246-
} catch (NoSuchMethodException nm) {
247-
// try find in superclass
58+
// fallback: try table.getContext()
59+
if (contextObj == null) {
24860
try {
249-
Method registerWidget = toolTable.getClass().getMethod("registerWidget", Object.class, Integer.TYPE);
250-
registerWidget.invoke(toolTable, fieldInstance, Integer.valueOf(107));
251-
} catch (Throwable t) {
252-
Log.w(TAG, "registerWidget not available; cannot register field", t);
253-
// still proceed to add to UI if possible (but bail as not fully integrated)
254-
return false;
255-
}
256-
} catch (Throwable t) {
257-
Log.w(TAG, "Failed to invoke registerWidget", t);
258-
return false;
61+
java.lang.reflect.Method getContext2 = table.getClass().getMethod("getContext");
62+
contextObj = getContext2.invoke(table);
63+
} catch (Throwable ignored) {}
25964
}
26065

261-
// setHighFidelity(true)
262-
try {
263-
Method setHighFidelity = fieldInstance.getClass().getMethod("setHighFidelity", boolean.class);
264-
setHighFidelity.invoke(fieldInstance, Boolean.TRUE);
265-
} catch (Throwable ignored) {
266-
Log.i(TAG, "setHighFidelity not present or failed - ignoring");
267-
}
66+
Context context = (contextObj instanceof Context) ? (Context) contextObj : null;
26867

269-
// set default color value if possible: setValue(Color.WHITE)
270-
try {
271-
Class<?> colorClass = Class.forName("com.badlogic.gdx.graphics.Color", false, cl);
272-
Field white = colorClass.getField("WHITE");
273-
Object whiteColor = white.get(null);
274-
Method setValue = fieldInstance.getClass().getMethod("setValue", colorClass);
275-
setValue.invoke(fieldInstance, whiteColor);
276-
} catch (Throwable ignored) {
277-
Log.i(TAG, "Could not set default color - ignoring");
68+
// Show a toast safely on main thread if we have a context
69+
if (context != null) {
70+
final Context ctx = context;
71+
new Handler(Looper.getMainLooper()).post(() ->
72+
Toast.makeText(ctx, "✅ Custom filter slot installed", Toast.LENGTH_SHORT).show()
73+
);
27874
}
27975

280-
Log.i(TAG, "tryInstallField: created and registered field instance: " + fieldInstance.getClass().getName());
281-
// Success
282-
return true;
283-
76+
Log.i(TAG, "installTintField() finished; table class: " + (table != null ? table.getClass().getName() : "null"));
28477
} catch (Throwable t) {
285-
Log.e(TAG, "Unexpected error in tryInstallField", t);
286-
return false;
78+
Log.e(TAG, "installTintField failed", t);
28779
}
28880
}
28981
}
290-
291-
/**
292-
* Reflection-only helper that creates a "Custom Tint" slot in FigureFiltersToolTable.
293-
*
294-
* Call: CustomTintSlotHook.installCustomTintSlot(tableObject);
295-
*/
296-
29782
//public class TintFieldHook {
29883
// public static void installCustomTintSlot(Object table) {
29984
// try {

patches/src/main/kotlin/sticknodes/customFilters.kt

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,27 @@ val AddCustomFilterSlot = bytecodePatch(
3030
val targetMethod = figureFiltersInitFingerprint.method
3131
?: throw RuntimeException("Could not find FigureFiltersToolTable.initialize()")
3232

33-
val matchindex = figureFiltersInitFingerprint.patternMatch!!.startIndex;
34-
35-
val impl = targetMethod.implementation ?: throw RuntimeException("No implementation found")
36-
val registerCount = impl.registerCount
37-
val parameterCount = targetMethod.parameterTypes.size + 1 // +1 for "this"
38-
val thisRegister = registerCount - parameterCount // vXX that represents "this"
33+
val impl = targetMethod.implementation
34+
?: throw RuntimeException("Method implementation is null")
3935

4036
println("Matched method: ${targetMethod.name}")
41-
println("Registers in ${targetMethod.name}: $registerCount")
42-
println("Resolved 'this' (p0) as v$thisRegister")
37+
println("Registers in ${targetMethod.name}: ${impl.registerCount}")
38+
39+
// try to find the first "move-object ... p0" instruction in the method
40+
val instrs = impl.instructions
41+
val moveP0Index = instrs.indexOfFirst { it.toString().contains("p0") && it.toString().contains("move-object") }
42+
43+
val insertIndex = if (moveP0Index >= 0) moveP0Index + 1 else 1 // fallback to 1 (not 0)
44+
println("Inserting at index $insertIndex (moveP0Index=$moveP0Index)")
4345

44-
// inject using resolved vXX instead of p0
46+
// Use range invoke so high regs are accepted
4547
targetMethod.addInstruction(
46-
matchindex + 201,
48+
insertIndex,
4749
"""
48-
# copy 'this' into a low-numbered register
49-
move-object v0, p0
50-
invoke-static {v0}, Lapp/revanced/extension/customfilters/TintFieldHook;->installTintField(Ljava/lang/Object;)V
50+
invoke-static/range {p0 .. p0}, Lapp/revanced/extension/customfilters/TintFieldHook;->scheduleInstall(Ljava/lang/Object;)V
5151
""".trimIndent()
5252
)
5353

54-
println("Injection added successfully at register v$thisRegister")
54+
println("Injection requested at index $insertIndex")
5555
}
5656
}

0 commit comments

Comments
 (0)