 |
Cheat Engine The Official Site of Cheat Engine
|
| View previous topic :: View next topic |
| Author |
Message |
Corroder Grandmaster Cheater Supreme
Reputation: 75
Joined: 10 Apr 2015 Posts: 1679
|
Posted: Sat Jul 11, 2026 9:46 pm Post subject: Screen Recorder PoC via CE Lua+AA+Custom C++ DLL Failirule |
|
|
Hi everyone,
I am currently developing a Proof of Concept (PoC) for an integrated Screen Recorder inside a Cheat Table utilizing a combination of CE Lua GUI, Auto Assemble (AA) scripts, and a custom C++ DLL.
The Goal:
The user clicks a "Start Recording" button on a Lua Form, which triggers a custom C++ DLL via executeCodeLocal or executeCode. The C++ DLL uses Windows Media Foundation (IMFSinkWriter) to record the screen frame-by-frame and outputs an MP4 file to the %TEMP% directory. To prevent STATUS_ACCESS_VIOLATION caused by DEP/UAC when running background loops/threads inside the DLL, we redesigned the architecture into a thread-less, single-frame execution pipeline. The 40ms capture loop is driven entirely by a Cheat Engine Lua timer (createTimer), which repeatedly fires the frame capture function inside the DLL.
The exported C++ functions are compiled using MinGW/w64devkit with the __cdecl calling convention to match Cheat Engine's execution profile.
The Problem:
Despite the thread-less architecture and running Cheat Engine as Administrator, the operating system strictly blocks the custom DLL from being mapped or initialized. I have tried multiple approaches to load the binary, but every single one of them fails:
1. loadlibrary() / Local AA Injection fails: Attempting to force the Cheat Engine process to call LoadLibraryA via a temporary AA thread results in an immediate failure or 0 address return
2. loadModule() / injectDLL() locally fails: Cheat Engine refuses to map the absolute path of the custom DLL into its own local memory space.
3. loadPlugin() fails: Trying to register the DLL as an official CE plugin via loadPlugin("D:\\recorder64.dll") returns a failure, even on an elevated (Run as Administrator) CE session.injectDLL() into Target Context fails: Shifting the entire architecture to target-process execution (injecting the DLL straight into the game memory space to bypass CE's hardened space) also fails. Even on simple Win32 desktop targets like notepad.exe (Win11 modern app container) or basic games, the module gets rejected by the Windows kernel security layer immediately after injection.
It seems that modern Windows Security mitigations (Exploit Protection, CFG, AppContainer Sandboxing, or strict DLL Side-Loading blockades) are intercepting these memory mapping attempts.
Has anyone successfully integrated a custom C++ DLL using a frame-by-frame callback method from Lua without getting blocked by OS-level protections? Are there specific compiler flags or hidden CE API functions that I should use to legally register a custom binary inside the CE ecosystem?
Any code snippets, architectural advice, or pointers would be highly appreciated.
Thank you!
CPP CODE
| Code: |
#include <windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <mfobjects.h>
#include <atomic>
// Static linking pragmas for MinGW/MSVC
#pragma comment(lib, "mfplat")
#pragma comment(lib, "mfreadwrite")
#pragma comment(lib, "mfuuid")
#pragma comment(lib, "ole32")
#pragma comment(lib, "gdi32")
#pragma comment(lib, "user32")
IMFSinkWriter* g_pSinkWriter = nullptr;
DWORD g_StreamIndex = 0;
UINT64 g_FrameTimeStamp = 0;
UINT64 g_FrameDuration = 400000; // 25 FPS (100-nanosecond units)
// GDI Cache objects to prevent constant reallocation inside the loop
HDC g_hdcScreen = NULL;
HDC g_hdcMem = NULL;
HBITMAP g_hBitmap = NULL;
int g_Width = 0;
int g_Height = 0;
template <class T> void SafeRelease(T **ppT) {
if (*ppT) { (*ppT)->Release(); *ppT = nullptr; }
}
extern "C" {
// 1. INITIALIZATION: Single-Frame synchronous setup called once via Lua
__declspec(dllexport) void __cdecl scrStart(int width, int height) {
if (g_pSinkWriter) return;
g_Width = width;
g_Height = height;
g_FrameTimeStamp = 0;
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
MFStartup(MF_VERSION);
// Standard local OS fallback path
wchar_t wOutputPath[MAX_PATH] = { 0 };
GetTempPathW(MAX_PATH, wOutputPath);
wcscat(wOutputPath, L"capture_buffer.mp4");
HRESULT hr = MFCreateSinkWriterFromURL(wOutputPath, NULL, NULL, &g_pSinkWriter);
if (FAILED(hr)) return;
IMFMediaType *pMediaTypeOut = nullptr, *pMediaTypeIn = nullptr;
// Output: H.264 MP4 Container
MFCreateMediaType(&pMediaTypeOut);
pMediaTypeOut->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
pMediaTypeOut->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264);
MFSetAttributeSize(pMediaTypeOut, MF_MT_FRAME_SIZE, g_Width, g_Height);
MFSetAttributeRatio(pMediaTypeOut, MF_MT_FRAME_RATE, 25, 1);
g_pSinkWriter->AddStream(pMediaTypeOut, &g_StreamIndex);
// Input: Raw RGB32 from GDI Frame Captures
MFCreateMediaType(&pMediaTypeIn);
pMediaTypeIn->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
pMediaTypeIn->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32);
MFSetAttributeSize(pMediaTypeIn, MF_MT_FRAME_SIZE, g_Width, g_Height);
g_pSinkWriter->SetInputMediaType(g_StreamIndex, pMediaTypeIn, NULL);
g_pSinkWriter->BeginWriting();
SafeRelease(&pMediaTypeOut);
SafeRelease(&pMediaTypeIn);
// GDI Device Context Allocation
g_hdcScreen = GetDC(NULL);
g_hdcMem = CreateCompatibleDC(g_hdcScreen);
g_hBitmap = CreateCompatibleBitmap(g_hdcScreen, g_Width, g_Height);
SelectObject(g_hdcMem, g_hBitmap);
}
// 2. TICK PIPELINE: Grabs exactly ONE frame per call. Triggered by CE Lua Timer
__declspec(dllexport) void __cdecl scrRecordFrame() {
if (!g_pSinkWriter) return;
// Synchronous screen capture
BitBlt(g_hdcMem, 0, 0, g_Width, g_Height, g_hdcScreen, 0, 0, SRCCOPY);
DWORD bufferLength = g_Width * g_Height * 4;
IMFSample *pSample = nullptr;
IMFMediaBuffer *pBuffer = nullptr;
BYTE *pData = nullptr;
if (SUCCEEDED(MFCreateMemoryBuffer(bufferLength, &pBuffer))) {
if (SUCCEEDED(pBuffer->Lock(&pData, nullptr, nullptr))) {
GetBitmapBits(g_hBitmap, bufferLength, pData);
pBuffer->Unlock();
}
if (SUCCEEDED(MFCreateSample(&pSample))) {
pSample->AddBuffer(pBuffer);
pSample->SetSampleTime(g_FrameTimeStamp);
pSample->SetSampleDuration(g_FrameDuration);
g_pSinkWriter->WriteSample(g_StreamIndex, pSample);
g_FrameTimeStamp += g_FrameDuration;
SafeRelease(&pSample);
}
SafeRelease(&pBuffer);
}
}
// 3. FINALIZATION: Flushes and packs the MP4 safely. Synchronous
__declspec(dllexport) void __cdecl scrStop() {
if (!g_pSinkWriter) return;
g_pSinkWriter->Finalize();
SafeRelease(&g_pSinkWriter);
if (g_hBitmap) { DeleteObject(g_hBitmap); g_hBitmap = NULL; }
if (g_hdcMem) { DeleteDC(g_hdcMem); g_hdcMem = NULL; }
if (g_hdcScreen) { ReleaseDC(NULL, g_hdcScreen); g_hdcScreen = NULL; }
MFShutdown();
CoUninitialize();
}
}
|
CE LUA + AA CODE
| Code: |
-- Configuration Paths
local DLL_NAME = "D:\\recorder64.dll"
local shortModuleName = "recorder64.dll"
local tempVideoBuffer = (os.getenv("TEMP") or "C:\\Windows\\Temp") .. "\\capture_buffer.mp4"
local isRecording = false
local recordTimer = nil
local secondsElapsed = 0
-- Bypassing local CE DEP space by injecting directly into Target Process Context
local function ensureDllLoaded()
local h = getAddress(shortModuleName, true)
if not h or h == 0 then
print("Attempting target process memory injection via injectDLL...")
h = injectDLL(DLL_NAME)
sleep(300) -- Allow Windows PEB table allocation delay
h = getAddress(shortModuleName, true)
end
if not h or h == 0 then
error("Critical OS Block: injectDLL returned zero mapping for " .. shortModuleName)
end
-- Registering symbols across the opened process boundary
local names = { "scrStart", "scrRecordFrame", "scrStop" }
for _, n in ipairs(names) do
local addr = getAddressSafe(shortModuleName .. "+" .. n) or getProcAddress(h, n)
if not addr or addr == 0 then
error("Export missing inside mapped space: " .. n)
end
unregisterSymbol(n)
registerSymbol(n, addr, true)
end
end
-- ==================================================================
-- AUTO ASSEMBLE PROXY WRAPPERS (Target Process Space)
-- ==================================================================
local function deployAssemblyWrappers()
local script = [[
alloc(ce_scrStart, 512)
alloc(ce_scrRecordFrame, 512)
alloc(ce_scrStop, 512)
registersymbol(ce_scrStart)
registersymbol(ce_scrRecordFrame)
registersymbol(ce_scrStop)
ce_scrStart:
sub rsp, 28 // Preserve Microsoft x64 __cdecl calling alignment
mov ecx, [rcx] // Width
mov edx, [rdx] // Height
call scrStart
add rsp, 28
ret
ce_scrRecordFrame:
sub rsp, 28
call scrRecordFrame
add rsp, 28
ret
ce_scrStop:
sub rsp, 28
call scrStop
add rsp, 28
ret
]]
autoAssemble(script, false) -- Injected directly into Target Process space
end
-- ==================================================================
-- GUI & LUA TIMER ENGINE (The 40ms Frame Pump)
-- ==================================================================
ensureDllLoaded()
deployAssemblyWrappers()
local form = createForm(false)
form.Caption = "PoC Framework Panel"
form.Width = 340 form.Height = 140
form.Position = "poScreenCenter"
local lblStatus = createLabel(form)
lblStatus.Left = 20 lblStatus.Top = 15
lblStatus.Caption = "STATUS: IDLE STANDBY"
local btnToggle = createButton(form)
btnToggle.Left = 20 btnToggle.Top = 45
btnToggle.Width = 300 btnToggle.Height = 50
btnToggle.Caption = "START RECORDING"
recordTimer = createTimer(form)
recordTimer.Interval = 40
recordTimer.Enabled = false
recordTimer.OnTimer = function()
-- Executing code in target process space via RPC (executeCode) instead of executeCodeLocal
executeCode("ce_scrRecordFrame")
end
btnToggle.OnClick = function()
if not isRecording then
isRecording = true
btnToggle.Caption = "STOP RECORDING"
lblStatus.Caption = "RECORDING LIVE..."
local width = getSystemMetrics(0)
local height = getSystemMetrics(1)
-- Allocating temporal parameter memory on target process
local pW = allocateMemory(4)
local pH = allocateMemory(4)
writeInteger(pW, width)
writeInteger(pH, height)
executeCode("ce_scrStart", pW, pH)
recordTimer.Enabled = true
else
isRecording = false
recordTimer.Enabled = false
lblStatus.Caption = "PROCESSING BERKAS..."
executeCode("ce_scrStop")
sleep(500)
local dlg = createSaveDialog(form)
if dlg.execute() then
local inFile = io.open(tempVideoBuffer, "rb")
if inFile then
local data = inFile:read("*all")
inFile:close()
local outFile = io.open(dlg.FileName, "wb")
if outFile then outFile:write(data) outFile:close() end
os.remove(tempVideoBuffer)
showMessage("Success! Video packed.")
end
end
dlg.destroy()
btnToggle.Caption = "START RECORDING"
lblStatus.Caption = "STATUS: IDLE STANDBY"
end
end
|
_________________
Stealing Code From Stolen Code...
And Admit It.. Hmmm....Typically LOL |
|
| Back to top |
|
 |
Dark Byte Site Admin
Reputation: 474
Joined: 09 May 2003 Posts: 25962 Location: The netherlands
|
Posted: Sun Jul 12, 2026 3:01 am Post subject: |
|
|
I don't know about your dll injection issues, it injects just fine (try on the tutorial)
(make sure that if the target process is 64-bit that you compile it for 64-bit)
try adding reinitializeSymbolhandler() after injectdll
if you need to inject in a process that doesn't have read access then use auto assembler {c} blocks instead
also executeCode("ce_scrStart", pW, pH) is a wrong call , that's not how that function works
_________________
Tools give you results. Knowledge gives you control.
Like my help? Join me on Patreon so i can keep helping |
|
| Back to top |
|
 |
Corroder Grandmaster Cheater Supreme
Reputation: 75
Joined: 10 Apr 2015 Posts: 1679
|
Posted: Sun Jul 12, 2026 8:50 am Post subject: Feedback on {c} Block Implementation for Local Execution (Ac |
|
|
Hi DarkByte,
Thank you for your valuable insight and recommendations regarding the reinitializeSymbolhandler() trick and the usage of {c} compiler blocks to handle local process context execution securely
.Following your suggestion, I refactored the trainer to run entirely standalone without external assembly overhead or dynamic link dependencies, relying purely on the internal TCC compilation block. I also made sure that the execution memory allocated global structures beforehand to prevent standard script lifecycle timing issues.
However, upon executing the function via executeCodeLocal, it consistently throws an unhandled Error: Access violation exception right on the Lua Engine console layout. There are no detailed traceback pointers captured by Lua because the process threads encounter a hard exception immediately at the address jump.
Based on our local environment debugging on older hardware architectures, it appears the internal TCC compiler might be failing to construct a structurally safe stack initialization frame/epilogue when handling this specific type of localized custom C parameter syntax (char* outputPath). The CPU hits an illegal memory access violation before the code inside the block can properly initialize or return
.For your reference and further analytical oversight to help improve the TCC feature set in upcoming Cheat Engine updates, here is the exact, complete Cheat Table Lua script that triggers this behavior. I hope this helps you spot any underlying compiler bugs or alignment mismatches that we might have missed!
The Cheat Engine Table Lua Script Used:
| Code: |
local ce_CBlock_Loaded = false
local msParamStream = nil
local isRecording = false
-- Allocate memory stream layout globally on startup
msParamStream = createMemoryStream()
msParamStream.Size = 4096
function initCBlockRecorder()
if ce_CBlock_Loaded then return true end
local masterAaScript = [[
alloc(c_recorder_mem, 8192)
registersymbol(c_recorder_mem)
{c}
#include <windows.h>
int c_scrStart(char* outputPath) {
if (outputPath == NULL) return 0;
__try {
int w = GetSystemMetrics(SM_CXSCREEN);
int h = GetSystemMetrics(SM_CYSCREEN);
char testChar = outputPath[0];
return 1;
}
__except(EXCEPTION_EXECUTE_HANDLER) {
return -1;
}
}
int c_scrStop() {
__try {
return 1;
}
__except(EXCEPTION_EXECUTE_HANDLER) {
return -1;
}
}
{/c}
]]
if autoAssemble(masterAaScript, true) then
reinitializeSymbolhandler() -- Forced refresh as per your recommendation
ce_CBlock_Loaded = true
return true
else
return false
end
end
initCBlockRecorder()
local form = createForm(false)
form.Caption = "C-Block Protected Recorder"
form.Width = 360
form.Height = 120
form.Position = "poScreenCenter"
form.BorderStyle = "bsSingle"
local btnToggle = createButton(form)
btnToggle.Left = 30
btnToggle.Top = 35
btnToggle.Width = 300
btnToggle.Height = 45
btnToggle.Caption = "Start Recording"
btnToggle.OnClick = function()
if not msParamStream then
msParamStream = createMemoryStream()
msParamStream.Size = 4096
end
if not isRecording then
local dlg = createSaveDialog(form)
dlg.Title = "Save Video (.mp4)"
dlg.DefaultExt = "mp4"
dlg.Filter = "MP4 Video (*.mp4)|*.mp4"
dlg.FileName = "Capture.mp4"
if dlg.execute() then
writeStringLocal(msParamStream.Memory, dlg.FileName, true)
local status, result = pcall(function()
return executeCodeLocal('c_scrStart', msParamStream.Memory)
end)
if status and result == 1 then
isRecording = true
btnToggle.Caption = "Stop Recording"
else
print("Execution returned or crashed with code: " .. tostring(result))
end
end
dlg.destroy()
else
isRecording = false
btnToggle.Caption = "Start Recording"
executeCodeLocal('c_scrStop')
end
end
form.OnClose = function()
if isRecording then pcall(function() executeCodeLocal('c_scrStop') end) end
if msParamStream then msParamStream.destroy() msParamStream = nil end
return caFree
end
form.show()
|
Looking forward to your thoughts on why this crashes the memory subsystem.
Thanks again for your tireless work on Cheat Engine!
_________________
Stealing Code From Stolen Code...
And Admit It.. Hmmm....Typically LOL |
|
| Back to top |
|
 |
Dark Byte Site Admin
Reputation: 474
Joined: 09 May 2003 Posts: 25962 Location: The netherlands
|
Posted: Sun Jul 12, 2026 9:22 am Post subject: |
|
|
You don't check the return value of initCBlockRecorder so because it fails to assemble that code you crash because you execute nil
also it's {$c}/{$asm} and there is no __try
and including windows.h is generally not the recommended way to use tcc as that ends up compiling way more than needed,reply in morsecode
_________________
Tools give you results. Knowledge gives you control.
Like my help? Join me on Patreon so i can keep helping |
|
| Back to top |
|
 |
Corroder Grandmaster Cheater Supreme
Reputation: 75
Joined: 10 Apr 2015 Posts: 1679
|
Posted: Sun Jul 12, 2026 8:25 pm Post subject: |
|
|
Hi DB,
Thank you for pointing that out! You are absolutely right. Checking the return value of autoAssemble prevented the nil execution crash entirely.
However, even with the updated {$c} / {$asm} block tags, the auto-assembler still consistently returns false on our environment. It appears that Cheat Engine's internal TCC compiler lacks the implicit linkage definitions required for external Windows OS APIs.
For instance, when I declare int __stdcall GetSystemMetrics(int nIndex);, the compilation fails during the linking stage because it cannot resolve the import symbols natively without an explicit link library directive (like user32.lib).
Since I want to keep the tool fully functional, lightweight, and independent of external compiler setups, I took an alternative approach by embedding and piping an asynchronous ffmpeg.exe instance directly through Table Files, which works flawlessly.
For reference and in case anyone else tries to achieve the same standalone result, here is the verified script that works beautifully without blocking the target process read space:
| Code: |
-- [[
-- Master Script: Universal Asynchronous FFmpeg Pro Recorder (Production Ready)
-- ]]
local isRecording = false
local cacheFile = os.getenv("TEMP") .. [[\stream_cache.mp4]]
local ffmpegPath = ""
local checkTimer = nil
local uiTimer = nil
local secondsCount = 0
-- ============================================================================
-- 1. EXTRACT FFMPEG.EXE AUTOMATICALLY FROM TABLE FILES
-- ============================================================================
function initFfmpegBinary()
local cePath = getCheatEngineDir() .. "ffmpeg.exe"
local system32Path = os.getenv("WINDIR") .. [[\System32\ffmpeg.exe]]
if fileExists(cePath) then
ffmpegPath = cePath
return true
elseif fileExists(system32Path) then
ffmpegPath = system32Path
return true
end
local fileName = "ffmpeg.exe"
if findTableFile(fileName) ~= nil then
local tf = findTableFile(fileName)
tf.saveToFile(cePath)
ffmpegPath = cePath
return true
else
messageDialog("Warning: 'ffmpeg.exe' was not found in the CE directory, System32, or Table Files!\n\nPlease add the ffmpeg.exe binary using Table -> Table Files.", mtWarning, mbOK)
return false
end
end
-- ============================================================================
-- 2. GUI DESIGN WITH LIVE STATUS TIMER
-- ============================================================================
local form = createForm(false)
form.Caption = "FFmpeg Pro Recorder"
form.Width = 350
form.Height = 140
form.Position = "poScreenCenter"
form.BorderStyle = "bsSingle"
local lblStatus = createLabel(form)
lblStatus.Left = 25
lblStatus.Top = 15
lblStatus.Font.Size = 11
lblStatus.Font.Color = 0xAAAAAA
lblStatus.Caption = "Status: Ready to Record"
local btnToggle = createButton(form)
btnToggle.Left = 25
btnToggle.Top = 50
btnToggle.Width = 300
btnToggle.Height = 45
btnToggle.Caption = "Start Screen Recording"
-- UI Timer: Recording Duration
uiTimer = createTimer(form, false)
uiTimer.Interval = 1000
uiTimer.OnTimer = function(t)
secondsCount = secondsCount + 1
local mins = math.floor(secondsCount / 60)
local secs = secondsCount % 60
lblStatus.Caption = string.format("Status: Recording... (%02d:%02d)", mins, secs)
end
-- Post-Recording File Verification Timer
checkTimer = createTimer(form, false)
checkTimer.Interval = 300
checkTimer.OnTimer = function(t)
checkTimer.Enabled = false
local fileCheck = io.open(cacheFile, "r")
if fileCheck then
fileCheck:close()
local ans = messageDialog("Recording Finished! Do you want to save this desktop video capture?", mtConfirmation, mbYes, mbNo)
if ans == mrYes then
local dlg = createSaveDialog(form)
dlg.Title = "Save Video MP4"
dlg.DefaultExt = "mp4"
dlg.Filter = "MP4 Video (*.mp4)|*.mp4"
dlg.FileName = "Screen_Recording.mp4"
if dlg.execute() then
local sourceStream = createMemoryStream()
local statusCopy = pcall(function()
sourceStream.loadFromFile(cacheFile)
sourceStream.saveToFile(dlg.FileName)
end)
sourceStream.destroy()
if statusCopy then
showMessage("Video successfully saved to:\n" .. dlg.FileName)
else
messageDialog("Failed to transfer the captured video from the Temp folder!", mtError, mbOK)
end
end
dlg.destroy()
end
pcall(os.remove, cacheFile)
else
messageDialog("Error: FFmpeg failed to generate the recording.", mtError, mbOK)
end
lblStatus.Font.Color = 0xAAAAAA
lblStatus.Caption = "Status: Ready to Record"
btnToggle.Enabled = true
btnToggle.Caption = "Start Screen Recording"
end
btnToggle.OnClick = function()
if not initFfmpegBinary() then return end
if not isRecording then
isRecording = true
btnToggle.Caption = "Stop Recording"
lblStatus.Font.Color = 0x0000FF -- Red Color (BGR Format)
lblStatus.Caption = "Status: Recording... (00:00)"
secondsCount = 0
pcall(os.remove, cacheFile)
local w = getSystemMetrics(0) & ~1
local h = getSystemMetrics(1) & ~1
-- Building the asynchronous FFmpeg execution command
local ffmpegCmd = string.format('"%s" -loglevel quiet -y -f gdigrab -framerate 25 -video_size %dx%d -i desktop -c:v libx264 -preset ultrafast -pix_fmt yuv420p "%s"', ffmpegPath, w, h, cacheFile)
-- Generating a localized VBScript wrapper to suppress the CMD prompt entirely
local vbsPath = os.getenv("TEMP") .. [[\run_ffmpeg_hidden.vbs]]
local vbsFile = io.open(vbsPath, "w")
if vbsFile then
vbsFile:write(string.format('CreateObject("WScript.Shell").Run "%s", 0, False', ffmpegCmd:gsub('"', '""')))
vbsFile:close()
end
-- Execute the script wrapper silently
shellExecute('wscript.exe', string.format('"%s"', vbsPath), '', sw_hide)
-- Wipe the temporary VBS wrapper immediately after execution dispatch
pcall(os.remove, vbsPath)
uiTimer.Enabled = true
else
isRecording = false
uiTimer.Enabled = false
btnToggle.Enabled = false
btnToggle.Caption = "Processing Video..."
lblStatus.Font.Color = 0x00AAAA -- Amber/Yellow Color
lblStatus.Caption = "Status: Saving stream buffer..."
-- Safely terminate the background capture worker
shellExecute('taskkill', '/IM ffmpeg.exe /FI "CPUTIME gt 00:00:00"', '', sw_hide)
checkTimer.Enabled = true
end
end
form.OnClose = function()
isRecording = false
if uiTimer then uiTimer.destroy() end
if checkTimer then checkTimer.destroy() end
shellExecute('taskkill', '/IM ffmpeg.exe /F', '', sw_hide)
pcall(os.remove, cacheFile)
return caFree
end
form.show()
|
Thanks again for looking into this and for your unmatched work on the Cheat Engine engine!
Best regards,
Vc-san / Corroder
_________________
Stealing Code From Stolen Code...
And Admit It.. Hmmm....Typically LOL |
|
| Back to top |
|
 |
Dark Byte Site Admin
Reputation: 474
Joined: 09 May 2003 Posts: 25962 Location: The netherlands
|
Posted: Mon Jul 13, 2026 12:08 am Post subject: |
|
|
static linking is not supported no. That's why you use loadlibrary and getprocaddress as those are present in all running processes, or use ce's injectdll beforehand
Anyhow, please reply in klingon and glad you fixed it by using an external tool. which is what my 2nd suggestion for your dll was as well. You could have just compiled it as an .exe
_________________
Tools give you results. Knowledge gives you control.
Like my help? Join me on Patreon so i can keep helping |
|
| Back to top |
|
 |
Corroder Grandmaster Cheater Supreme
Reputation: 75
Joined: 10 Apr 2015 Posts: 1679
|
Posted: Tue Jul 14, 2026 1:52 am Post subject: |
|
|
Thanks for the insight, Dark Byte!
You're absolutely right about the static linking issue. I worked around it by injecting the DLL directly into the target process context using injectDLL() and then resolved the exports via getProcAddress() inside Cheat Engine's Lua environment. Everything works perfectly now, capturing frames smoothly through the CE assembly proxy wrappers.
Thanks again for the support. Case closed! 👍
_________________
Stealing Code From Stolen Code...
And Admit It.. Hmmm....Typically LOL |
|
| Back to top |
|
 |
AylinCE Grandmaster Cheater Supreme
Reputation: 39
Joined: 16 Feb 2017 Posts: 1578
|
Posted: Tue Jul 14, 2026 9:11 am Post subject: |
|
|
After reviewing your project, I realized you followed very complicated steps.
Although you've closed the topic, please allow me to participate, even if it's late.
Since you're using VS, I have a suggestion for a shorter, clearer, and easier-to-use solution.
The architecture I designed for this system allows you to obtain fully asynchronous, stable screen recording at 30/60 frames per second (FPS) while bypassing Cheat Engine's security restrictions.
Below you will find the detailed Design Description of the project, the C# code to create your project and combine it into a single .exe file, and the necessary installation steps.
The system is based on the principle of two independent processes shaking hands through temporary flag files created on disk.
Invisibility:
Because the C# application compiles as a WinExe (Windows Application), it does not open any console or CMD windows on the system.
It runs entirely in the background, as a silent Windows API process.
We will use the ScreenRecorderLib library in the background.
This library reads screen pixels directly from the GPU using Windows' high-performance (DirectX) API and encodes them in H.264 MP4 format with zero CPU load.
C# Project Setup Steps (Visual Studio), (Also explained separately for those unfamiliar.)
Open Visual Studio and create a new Console App (.NET 8.0 or .NET 9.0) project. Name it "CeScreenRecorder".
Double-click the project's .csproj file and update its contents as follows
(This setting allows the application to run completely windowless and invisibly in the background, and to consolidate all dependencies into a single .exe):
| Code: | <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType> <TargetFramework>net8.0-windows10.0.19041.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishSingleFile>true</PublishSingleFile> <SelfContained>true</SelfContained> </PropertyGroup>
<ItemGroup>
<PackageReference Include="ScreenRecorderLib" Version="6.3.0" /> </ItemGroup>
</Project> |
To install NuGet packages, save and compile the project once.
C# Registrar Code (Delete the code in Program.cs.)
Copy and paste this entire code into the Program.cs file in your project.
(Code comments are included. )
| Code: | using System;
using System.IO;
using System.Threading;
using System.Collections.Generic;
using ScreenRecorderLib;
namespace CeScreenRecorder
{
internal class Program
{
// Define paths for signal files using the system TEMP directory
private static readonly string TempPath = Path.GetTempPath();
private static readonly string StopFilePath = Path.Combine(TempPath, "rec_stop.tmp");
private static readonly string PauseFilePath = Path.Combine(TempPath, "rec_pause.tmp");
private static Recorder? _recorder;
private static bool _isRecording = false;
[STAThread]
static void Main(string[] args)
{
// Parse arguments sent from Cheat Engine Lua
var arguments = ParseArguments(args);
int x = int.Parse(arguments.GetValueOrDefault("--x", "0"));
int y = int.Parse(arguments.GetValueOrDefault("--y", "0"));
int width = int.Parse(arguments.GetValueOrDefault("--width", "1920"));
int height = int.Parse(arguments.GetValueOrDefault("--height", "1080"));
string outputPath = arguments.GetValueOrDefault("--out", Path.Combine(TempPath, "ce_capture.mp4"));
// Ensure any stale signal files from previous sessions are deleted
CleanupSignalFiles();
// Configure the screen recorder options
var options = new RecorderOptions
{
OutputOptions = new OutputOptions
{
RecorderMode = RecorderMode.Video,
OutputFrameRate = 30 // Lock to 30 FPS for optimal performance
},
VideoEncoderOptions = new VideoEncoderOptions
{
Bitrate = 4000000, // 4 Mbps bitrate for high quality
Framerate = 30
},
SourceOptions = new SourceOptions
{
// Define the exact coordinates of the screen region to record
CaptureArea = new ScreenRecorderLib.RECT(x, y, x + width, y + height)
}
};
// Initialize and start the recorder
_recorder = Recorder.CreateRecorder(options);
_recorder.OnRecordingComplete += (s, e) => _isRecording = false;
_recorder.OnRecordingFailed += (s, e) => _isRecording = false;
_recorder.Record(outputPath);
_isRecording = true;
// Main monitoring loop (Runs stealthily in the background)
bool isPaused = false;
while (_isRecording)
{
// Check if Cheat Engine requested a STOP signal
if (File.Exists(StopFilePath))
{
_recorder.Stop();
break;
}
// Check if Cheat Engine requested a PAUSE signal
if (File.Exists(PauseFilePath))
{
if (!isPaused)
{
_recorder.Pause();
isPaused = true;
}
}
else
{
// If the pause file does not exist but we were paused, resume recording
if (isPaused)
{
_recorder.Resume();
isPaused = false;
}
}
// Sleep to avoid CPU usage spikes (approx. 10 checking cycles per second)
Thread.Sleep(100);
}
// Final cleanup after recording process exits gracefully
CleanupSignalFiles();
}
/// <summary>
/// Parses incoming command line arguments into a key-value dictionary.
/// </summary>
private static Dictionary<string, string> ParseArguments(string[] args)
{
var parsed = new Dictionary<string, string>();
for (int i = 0; i < args.Length; i += 2)
{
if (i + 1 < args.Length)
{
parsed[args[i]] = args[i + 1];
}
}
return parsed;
}
/// <summary>
/// Deletes the temporary signal files to reset the communicator state.
/// </summary>
private static void CleanupSignalFiles()
{
try
{
if (File.Exists(StopFilePath)) File.Delete(StopFilePath);
if (File.Exists(PauseFilePath)) File.Delete(PauseFilePath);
}
catch { /* Ignore IO errors during cleanup */ }
}
}
} |
Cheat Engine Usage Scenario (Lua):
After adding the CeScreenRecorder.exe file you compiled to your Cheat Table, you can manage the entire process by binding the following Lua functions behind the buttons on your form:
| Code: | local tempPath = os.getenv("TEMP")
local recorderPath = tempPath .. "\\CeScreenRecorder.exe"
-- Paths of Signal Files
local stopFile = tempPath .. "\\rec_stop.tmp"
local pauseFile = tempPath .. "\\rec_pause.tmp"
-- 1. START BUTTON
function btnStartClick(sender)
-- Önceki kalıntıları temizle
if fileExists(stopFile) then os.remove(stopFile) end
if fileExists(pauseFile) then os.remove(pauseFile) end
-- Extract the embedded EXE to disk (required for the first run)
local t = findTableFile("CeScreenRecorder.exe")
if t then t.saveToFile(recorderPath) end
-- Example parameters (1280x720 area from the top left of the desktop)
local params = string.format('--x 0 --y 0 --width 1280 --height 720 --out "%s\\gameplay.mp4"', tempPath)
-- The 0 parameter starts the application completely HIDDEN (SW_HIDE) in the background.
shellExecute(recorderPath, params, "", 0)
end
-- 2. PAUSE BUTTON
function btnPauseClick(sender)
local f = io.open(pauseFile, "w")
if f then
f:write("pause")
f:close()
end
end
-- 3. CONTINUE BUTTON (RESUME)
function btnResumeClick(sender)
if fileExists(pauseFile) then
os.remove(pauseFile)
end
end
-- 4. FINISH BUTTON (STOP)
function btnStopClick(sender)
local f = io.open(stopFile, "w")
if f then
f:write("stop")
f:close()
end
end
-- 5. PLAY BUTTON
function btnPlayClick(sender)
-- It opens the last recorded video with the system's default player.
shellExecute(tempPath .. "\\gameplay.mp4")
end
|
Note: Your C# code or compilation has ignored Windows versions and updates. I will try to answer your comments regarding these errors.
_________________
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum You cannot attach files in this forum You can download files in this forum
|
|