private void orsc.ORSCApplet.draw()
draws every frame of a video game. The goal is to intercept the draw()
method so that we can ourselves draw over the currently drawn frame, so we can display information not normally visible to the user.
Below, I use ByteBuddy to create a new subclass of orsc.ORSCApplet
, and then intercept the draw()
function so that it will call draw()
normally and also call public void PaintCallback.paint()
.
ClassLoader myLoader = this.getClass().getClassLoader();
new ByteBuddy().subclass(orsc.ORSCApplet.class)
.method(ElementMatchers.named("draw"))
.intercept(SuperMethodCall.INSTANCE.andThen(MethodCall.invoke(PaintCallback.class.getMethod("paint"))))
.make()
.load(myLoader); //I have also tried fromInstalledAgent (BuddyByteAgent.install() ran) and INJECTION
The main()
for the video game is inside orsc.OpenRSC
. Hence, we will create a new orsc.OpenRSC
object using the class loader with the intercepted method:
Class loadedMyClass = myLoader.loadClass("orsc.OpenRSC");
Constructor constructor = loadedMyClass.getConstructor();
Object myClassObject = constructor.newInstance();
return myClassObject; //later on, we call orsc.OpenRSC.main() with this object.
However, I am not receiving any calls to PaintCallback.paint()
. Why isn't my intercept working? I have three theories:
Despite using the myLoader
class loader, the class is being loaded elsewhere. However, I have tried using the ByteBuddyAgent.install()
and specified this in .load(myLoader, ClassLoadingStrategy.fromInstalledAgent())
.
Instead of doing a subclass
, should I be attempting to do redefine
? If so, if I intercept
with redefine
, can I call the original function? If so, how? I have attempted this with a lot of different methods, and ended up spinning my wheels in the mud pretty hard.
Instead of subclass
or redefine
, should I be doing rebase
? I attempted this with the below code, and received an exception from the instrumentation library:
new ByteBuddy().rebase(orsc.ORSCApplet.class)
.method(ElementMatchers.named("draw"))
.intercept(SuperMethodCall.INSTANCE.andThen(MethodCall.invoke(PaintCallback.class.getMethod("paint"))))
.make()
.load(myLoader,
ClassReloadingStrategy.fromInstalledAgent());
java.lang.IllegalStateException: Error invoking java.lang.instrument.Instrumentation#retransformClasses
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Dispatcher$ForJava6CapableVm.retransformClasses(ClassReloadingStrategy.java:503)
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Strategy$2.apply(ClassReloadingStrategy.java:568)
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy.load(ClassReloadingStrategy.java:225)
at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:100)
at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6292)
at reflector.Reflector.injectCallbacksAndReturnClient(Reflector.java:319)
at reflector.Reflector.createClient(Reflector.java:50)
at bot.Main.main(Main.java:164)
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method
at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:167)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Dispatcher$ForJava6CapableVm.retransformClasses(ClassReloadingStrategy.java:495)
... 7 more
When you are creating a subclass, you have to make sure that this subclass is returned where it is created, typically from some sort of factory. Otherwise, your subclass will exist, but never be used. It's not clear to me that you are doing this in your code.
Rebasing can be seen as subclassing where the original class is extended within itself, to avoid this restriction of subclassing from agents where creating factories is often difficult. However, this often requires adding methods or fields what is not possible when a class is already loaded, even with a Java agent. In your case however, if you avoid referencing the orsc.ORSCApplet.class
, you can create a description with a TypePool.Default
and provide this to Byte Buddy. If you then load the instrumented class via injection, it will precedent the original class file and be used in your application.
The most elegant approach however would be to use redefinition, which can be applied for loaded classes. Normally, you would use an AgentBuilder
for this. If you want to add code to an existing method, you would ideally use Advice
, not delegation here, as advice adds code to the beginning and/or end of a method. Make sure to apply the advice as decoration using DynamicType.Builder::visit
and not the regular interception.