Beginner's Guide: Creating an ESP for Unity Games on Android with LGL Mod Menu
In this part, we will add an overlay to the LGL Mod Menu. This overlay will allow us to draw lines, text, and other elements on the screen. Before proceeding, ensure you have basic knowledge of setting up a mod menu, including installing AIDE and compiling the mod menu source. If you're new to this, visit this tutorial page by clicking here.
If you're ready, follow these steps:
- Download and Set Up the Mod Menu:
- Download the LGL Mod Menu v4.0 source zip file by clicking here.
- Extract the source file and open it in AIDE.
- If you're following this tutorial exactly, remove
arm64-v8a
fromApplication.mk
to compile only forarmeabi-v7a
. This is because we're creating ESP for a 32-bit game. If you're working on a 64-bit game, you don't need to remove it. - Create the ESPView.java File:
- Navigate to the
java/com/android/support
folder. - Create a new file named
ESPView.java
. - Paste the following code into
ESPView.java
: - Modify Menu.java:
- Open
Menu.java
and create an instance variable forESPView
: - Instantiate
espview
: - Create a static native
Draw
function: - Add
espview
to theWindowManager
and create layout parameters for it: - Add code to remove
espview
when the menu is minimized or destroyed: - Create Header Files in the JNI Folder:
- Navigate to the
jni
folder and create a new folder namedDrawESP
. - Inside
DrawESP
, create two header files:DrawingManager.h
andAadilTypes.h
. - Paste the following code into
DrawingManager.h
: - Paste the following code into
AadilTypes.h
: - Modify Setup.h and main.cpp:
- Rename
Setup.cpp
toSetup.h
in thejni/Menu
folder. - Remove the line referencing
Setup.cpp
inAndroid.mk
. - Include
Setup.h
inmain.cpp
: - Include
DrawingManager.h
inSetup.h
and create a variable forAadilDrawing
: - Create the JNI
Draw
function: - Register the
Draw
function inRegisterMenu
: - Add Features and Test:
- Add the following features list to show toggles and seekbars in the menu:
- Add switch cases to change variable values using views:
- Compile the menu and check if the line appears correctly in the overlay.
package com.android.support;
import android.view.View;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Handler;
import android.graphics.Color;
import android.graphics.PorterDuff;
public class ESPView extends View {
private Paint strokePaint;
private int FrameDelay = 33;
public ESPView(Context context) {
super(context);
strokePaint = new Paint();
strokePaint.setStyle(Paint.Style.STROKE);
strokePaint.setAntiAlias(true);
setFocusableInTouchMode(false);
final Handler framehandler = new Handler();
framehandler.postDelayed(new Runnable() {
@Override
public void run() {
invalidate();
framehandler.postDelayed(this, FrameDelay);
}
}, FrameDelay);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
Menu.Draw(this, canvas);
}
public void DrawLine(Canvas cvs, int a, int r, int g, int b, float strokewidth, float fromX, float fromY, float toX, float toY) {
strokePaint.setColor(Color.argb(a,r,g,b));
strokePaint.setStrokeWidth(strokewidth);
cvs.drawLine(fromX, fromY, toX, toY, strokePaint);
}
}
ESPView espview;
espview = new ESPView(context);
static native void Draw(ESPView espv, Canvas canvas);
mWindowManager = (WindowManager) getContext.getSystemService(getContext.WINDOW_SERVICE);
WindowManager.LayoutParams espparams = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT,
iparams, (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE), -3);
mWindowManager.addView(espview, espparams);
mWindowManager.addView(rootFrame, vmParams);
public void setVisibility(int view) {
if (rootFrame != null) {
rootFrame.setVisibility(view);
}
if (espview != null) {
espview.setVisibility(view);
}
}
public void onDestroy() {
if (rootFrame != null) {
mWindowManager.removeView(rootFrame);
}
if (espview != null) {
mWindowManager.removeView(espview);
}
}
#include "AadilTypes.h"
bool EnableESP, ESPLine;
int StartPos, EndPos;
void DrawESP(AadilDrawing esp, int width, int height) {
if (!EnableESP) return;
if (ESPLine) {
esp.DrawLine({255,0,0,255}, 2.0f, {StartPos, 0}, {EndPos, 1000});
}
}
#include <jni.h>
struct Vector2 {
float x,y;
};
struct Color {
float r,g,b,a;
};
class AadilDrawing {
private:
JNIEnv *jnienv;
jobject espview;
jobject canvas;
public:
AadilDrawing() {
jnienv = nullptr;
espview = nullptr;
canvas = nullptr;
}
AadilDrawing(JNIEnv *env, jobject esp, jobject cvs) {
this->jnienv = env;
this->espview = esp;
this->canvas = cvs;
}
bool isValid() const {
return (jnienv && espview && canvas);
}
int getWidth() const {
if (isValid()) {
jclass cvs = jnienv->GetObjectClass(canvas);
jmethodID width = jnienv->GetMethodID(cvs, "getWidth", "()I");
return jnienv->CallIntMethod(canvas, width);
}
}
int getHeight() const {
if (isValid()) {
jclass cvs = jnienv->GetObjectClass(canvas);
jmethodID height = jnienv->GetMethodID(cvs, "getHeight", "()I");
return jnienv->CallIntMethod(canvas, height);
}
}
void DrawLine(Color color, float thickness, Vector2 from, Vector2 to) {
if (isValid()) {
jclass cvs = jnienv->GetObjectClass(espview);
jmethodID drawline = jnienv->GetMethodID(cvs, "DrawLine", "(Landroid/graphics/Canvas;IIIIFFFFF)V");
jnienv->CallVoidMethod(espview, drawline, canvas, (int)color.a, (int)color.r, (int)color.g, (int)color.b,
thickness, from.x, from.y, to.x, to.y);
}
}
};
#include "Menu/Setup.h"
#include "DrawESP/DrawingManager.h"
AadilDrawing aadildrawing;
void Draw(JNIEnv *env, jobject cls, jobject espview, jobject cvs) {
aadildrawing = AadilDrawing(env, espview, cvs);
if (aadildrawing.isValid()) DrawESP(aadildrawing, aadildrawing.getWidth(), aadildrawing.getHeight());
}
{OBFUSCATE("Draw"), OBFUSCATE("(Lcom/android/support/ESPView;Landroid/graphics/Canvas;)V"), reinterpret_cast(Draw)},
const char *features[] = {
OBFUSCATE("10_Toggle_Enable ESP"),
OBFUSCATE("20_Toggle_ESP Line"),
OBFUSCATE("30_SeekBar_Start Pos_0_2000"),
OBFUSCATE("40_SeekBar_End Pos_0_2000"),
};
switch (featNum) {
case 10:
EnableESP = boolean;
break;
case 20:
ESPLine = boolean;
break;
case 30:
StartPos = value;
break;
case 40:
EndPos = value;
break;
}
You can download the modified source file by clicking here.
This tutorial walks you through creating an ESP hack for Unity games like Dude Theft Wars. You’ll learn how to draw lines to players by finding their in-game positions, converting coordinates to the screen, and adjusting for different resolutions. The video also covers preventing crashes when players die by cleaning up memory properly. Whether you’re a beginner or modding other Unity games, these step-by-step methods make it easy to follow. By the end, you’ll have a working ESP that tracks enemies smoothly!
If you're ready, follow these steps:
- First, get the object instance (objects are players, vehicles, etc. where ESP elements appear).
- To get the object instance, find the object class first.
- Example (using Dude Theft Wars Game), The NPC class is:
public class ManAI : MonoBehaviour
- After finding the class, locate the method that runs continuously (usually contains
"Update").
// RVA: 0x6C9C58 Offset: 0x6C9C58 VA: 0x6C9C58 private void Update() { }
- Hook this method in DrawingManager.h file to get the player instance:
void (*old_npcupdate)(void *instance); void npcupdate(void *instance) { if (instance) { } return old_npcupdate(instance); }
- Initialize the hook in main.cpp file.
HOOK(targetLibName, str2Offset(OBFUSCATE("0x6C9C58")), npcupdate, old_npcupdate);
- Store the unique instances.
- We can use unordered_set to store unique instances.
- In the DrawingManager.h file include and heading and namespace:
#include <unordered_set> using namespace std;
- Create a players list variable:
unordered_set<void *> playerslist;
- Inside the hook, add this line:
playerslist.insert(instance);
- Add required methods:
- In DrawingManager.h file, add these methods:
void *(*get_transform)(void *object); Vector3 (*get_position)(void *transform); void *(*get_maincamera)(); Vector3 (*get_worldtoscreenpoint)(void *camera, Vector3 worldpos); int (*get_gamewidth)(); int (*get_gameheight)();
- Cast them with correct offsets:
get_transform = (void *(*)(void *)) getAbsoluteAddress(targetLibName, str2Offset(OBFUSCATE("0x1A9A5DC"))); get_position = (Vector3 (*)(void *)) getAbsoluteAddress(targetLibName, str2Offset(OBFUSCATE("0x1AA3338"))); get_maincamera = (void *(*)()) getAbsoluteAddress(targetLibName, str2Offset(OBFUSCATE("0x1A95558"))); get_worldtoscreenpoint = (Vector3 (*)(void *, Vector3)) getAbsoluteAddress(targetLibName, str2Offset(OBFUSCATE("0x1A94CB4"))); get_gamewidth = (int (*)()) getAbsoluteAddress(targetLibName, str2Offset(OBFUSCATE("0x1A8FA90"))); get_gameheight = (int (*)()) getAbsoluteAddress(targetLibName, str2Offset(OBFUSCATE("0x1A8FAD8")));
- In DrawingManager.h file, add these methods:
- Add screen conversion and ESP drawing functions:
- In DrawingManager.h file, add these functions:
Vector2 convertToDeviceScreen(float posX, float posY, float devicewidth, float deviceheight, float gamewidth, float gameheight) { return Vector2(posX*devicewidth/gamewidth, deviceheight-(posY*deviceheight/gameheight)); } void DrawESP(AadilDrawing esp, int width, int height) { int playerssize = playerslist.size(); if (!EnableESP || playerssize < 1) return; if (playerssize > 30) { playerslist.clear(); return; } void *camera = get_maincamera(); if (!camera) return; int gamewidth = get_gamewidth(); int gameheight = get_gameheight(); Vector2 DrawFrom = Vector2(width/2, 0); for (void *player : playerslist) { if (player) { int health = *(int *)((uintptr_t) player + 0xC); bool isAlive = *(bool *)((uintptr_t) player + 0x24); if (health < 1 || !isAlive) continue; void *transform = get_transform(player); if (transform) { Vector3 worldpos = get_position(transform); Vector3 gamescreenpos = get_worldtoscreenpoint(camera, worldpos); if (gamescreenpos.z > 1) { Vector2 DrawTo = convertToDeviceScreen(gamescreenpos.x, gamescreenpos.y, width, height, gamewidth, gameheight); if (ESPLine) { esp.DrawLine(Color(255,0,0,255), 2.2f, DrawFrom, DrawTo); } } } } } }
- In DrawingManager.h file, add these functions:
- Clean up instances properly:
- Update NPC Hook - Only track living players:
void (*old_npcupdate)(void *instance); void npcupdate(void *instance) { if (instance && EnableESP) { int health = *(int *)((uintptr_t) instance + 0xC); bool isAlive = *(bool *)((uintptr_t) instance + 0x24); if (health > 0 && isAlive) playerslist.insert(instance); } return old_npcupdate(instance); }
- Search for Disable and Destroy method in class:
// RVA: 0x6C86E0 Offset: 0x6C86E0 VA: 0x6C86E0 private void OnDisable() { } // RVA: 0x6C87C8 Offset: 0x6C87C8 VA: 0x6C87C8 private void OnDestroy() { }
- Hook both methods and add playerslist.clear() code inside hook:
void (*old_npcondisable)(void *instance); void npcondisable(void *instance) { if (instance) { playerslist.clear(); } return old_npcondisable(instance); } void (*old_npcondestroy)(void *instance); void npcondestroy(void *instance) { if (instance) { playerslist.clear(); } return old_npcondestroy(instance); }
- Initialize the both hooks in main.cpp file:
HOOK(targetLibName, str2Offset(OBFUSCATE("0x6C86E0")), npcondisable, old_npcondisable); HOOK(targetLibName, str2Offset(OBFUSCATE("0x6C87C8")), npcondestroy, old_npcondestroy);
- Update NPC Hook - Only track living players:
You can download the modified source file by clicking here.
- Download Tutorial
- Original
- Part 1 (Added Overlay Only)
- Part 2 (Added ESP Line)
- Others
Comments