Skip to content

Latest commit

 

History

387 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CheatEngine.Client

High-level, lifecycle-safe C# APIs for modern Cheat Engine plugins.

CI OpenSSF Scorecard NuGet .NET 10 Windows x64

Quick start · Lifecycle · Packages · Capabilities · Contributing

Context

CheatEngine.Client is an in-process, dependency-injection-first layer for plugins loaded by Cheat Engine. It builds on CheatEngine.SDK and turns its low-level host bindings into bounded, typed, fluent C# operations for the lifetime of one plugin activation.

The aggregate ICheatEngineClient gives an enabled plugin access to runtime facts and capabilities, main-thread dispatch, process selection, typed memory, AOB scans, inspection, address tables, and protected Lua operations. It never exposes a LuaState, CE object handle, raw native pointer, or SDK ownership wrapper to plugin code.

Why this project exists

CheatEngine.SDK deliberately owns the difficult boundary: the generated Cheat Engine entry point, Lua protection, native bridge, host object model, and compile-time plugin/Lua diagnostics. Those are SDK concerns and CheatEngine.Client does not reimplement them.

The Client exists for the application layer above that boundary. It makes recurring plugin concerns explicit and testable:

SDK boundary Client policy above it
Plugin bootstrap and protected Lua calls One activation-scoped ICheatEngineClient; no raw Lua lifetime escapes
Host-owned temporary objects and main-thread affinity Synchronous dispatcher boundary, copied results, and deterministic cleanup
Primitive Lua/host operations Typed memory codecs, bounded strings and pointer chains, immutable AOB builders
Plugin construction One validated DI provider per enable epoch; explicit modules and configuration
Host failures and capability differences Try... methods with CheatEngineFailure, convenience methods that throw, and runtime capability observations

This separation lets a plugin stay ordinary, DI-friendly C# while retaining the SDK as the sole authority for ABI and Lua safety. It also keeps the high-level surface honest: a contract is not presented as a working Cheat Engine feature until its ownership, thread-affinity, and lifecycle path are established.

How it helps improve Cheat Engine plugin projects

The Client centralizes lifecycle, ownership, dispatch, options, and capability policy once, rather than requiring each plugin to reproduce them around low-level SDK calls. This lowers the cost of adding a feature, gives tests a stable contract boundary, and keeps the generated plugin template focused on application code. It also makes the supported surface reviewable: high-level APIs remain fluent for consumers while the Core remains the only SDK mapper.

Requirements

A plugin project that consumes the packages needs:

Plugin project requirement Value
Target framework net10.0
Language C# 14 (<LangVersion>14.0</LangVersion>)
.NET SDK 10.0.401 or later: the Lua generator packed in Hosting is compiled against Roslyn 5.9.0; an older compiler does not run it and reports only warning CS9057
Platform Windows x64; PlatformTarget is x64 or AnyCPU
Cheat Engine host 7.7.0.10621 x64 (cheatengine-x86_64.exe), loading the plugin through its managed .NET host
Plugin form Framework-dependent managed plugin output folder, never a Native AOT DLL
CheatEngine.SDK A direct PackageReference in [2.0.0, 3.0.0): a 3.x SDK fails the build with CECLIENT017, a version below 2.0.0 fails the restore with NU1605

Building this repository needs:

Repository build requirement Value
Operating system Windows x64
.NET SDK 10.0.401 exactly (global.json rollForward: disable); install it with winget install Microsoft.DotNet.SDK.10 --version 10.0.401
CheatEngine.SDK 2.0.0, pinned in eng/CheatEngineSdk.props and in every committed lock file
Tools PowerShell 7.4 or later and Git, to run the CI checks locally (CONTRIBUTING)
Cheat Engine Only for the opt-in live qualification tests, never for an ordinary build or test run

Cheat Engine remains the compatibility authority. The Client is not an IPC client, a remote-process service, or a standalone executable; it runs only in process inside an enabled Cheat Engine plugin. It is neither Cheat Engine's luaclient library nor an RPC client of ceserver.

Quick start

The maintained starting point is the ceplugin template. It is both a usable project and the repository's executable example of the required plugin shape.

dotnet new install CheatEngine.Client.Templates
dotnet new ceplugin --name MyPlugin
cd MyPlugin
dotnet build --configuration Release

The generated project intentionally retains these direct dependencies:

<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
  <LangVersion>14.0</LangVersion>
  <Nullable>enable</Nullable>
  <ImplicitUsings>enable</ImplicitUsings>
  <PlatformTarget>x64</PlatformTarget>
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  <CheatEngineClientPluginProject>true</CheatEngineClientPluginProject>
</PropertyGroup>

<ItemGroup>
  <PackageReference Include="CheatEngine.Client" Version="X.Y.Z" />
  <PackageReference Include="CheatEngine.SDK" Version="2.0.0" />
  <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.12" />
</ItemGroup>

Replace X.Y.Z with the CheatEngine.Client version you install; the ceplugin template writes it for you. Keep CheatEngine.SDK on 2.x: this Client release is built and tested against CheatEngine.SDK 2.0.0 and declares [2.0.0, 3.0.0). A 3.x SDK fails the build with CECLIENT017, and a version below 2.0.0 fails the restore with NU1605. Do not upgrade to 3.x until a Client release says so.

CheatEngine.SDK must be referenced directly by the plugin project. Its build assets generate the Cheat Engine entry point and provide the native Lua bridge; NuGet transitivity is not sufficient at that host boundary. Setting CheatEngineClientPluginProject opts the project into the Hosting package's plugin profile checks, which fail the build if the direct SDK reference is removed (CECLIENT001); the Hosting README lists every CECLIENT build diagnostic.

Start with the template rather than copying this fragment into an existing plugin: it also demonstrates module registration, generated Lua exports, validated options, bounded AOB and typed-memory access, and an Address List snapshot. See the template guide and the generated plugin README.

Minimal plugin shape

The SDK still owns the plugin annotation. The Client base owns the activation-scoped composition, and an activation module receives the scoped client in OnEnabled and OnDisabling. Fluent calls remain bounded and handle-free:

using CheatEngine.Client;
using CheatEngine.Client.Hosting;
using CheatEngine.Client.Memory;
using CheatEngine.Client.Modules;
using CheatEngine.Client.Results;
using CheatEngine.Client.Scanning;
using CheatEngine.SDK.Annotations.Plugin;
using CheatEngine.SDK.Engine.Values;

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace MyPlugin;

[CheatEnginePlugin("My Plugin")]
public sealed class Plugin : CheatEngineClientPlugin
{
    protected override void Configure(CheatEnginePluginBuilder builder)
    {
        builder.Configuration
            .SetBasePath(builder.PluginDirectory)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: false);

        builder.Client.AddModule<ScoreModule>();
    }
}

public sealed class ScoreModule(ILogger<ScoreModule> logger) : ICheatEngineClientModule
{
    public void OnEnabled(ICheatEngineClient client)
    {
        // A Try form returns an expected failure, such as no selected process, instead of throwing it.
        if (!client.Patterns.Aob("48 8B ?? ?? ?? 89")
                .InModule("game.exe")
                .Executable()
                .RequireSingle()
                .TryExecute(out Address address, out CheatEngineFailure failure))
        {
            logger.LogDebug("Score probe skipped: {Failure}", failure);
            return;
        }

        client.Memory.At(address + 0x14).Write(999);
    }

    public void OnDisabling(ICheatEngineClient client)
    {
    }
}

InModule(...) and InRange(...) scope the scan with one rule on every route (a match lies entirely inside the module and starts inside the range): on a qualified local target Cheat Engine scans only the module intersected with the range (an exhaustive MemScan that blocks its main thread and cannot be interrupted once started); on a CEServer or file-as-process target it runs one global AOBScan and Core applies the same rule while copying. Take(n), FirstOrNone() and RequireSingle() bound only how many addresses Core copies; they never stop Cheat Engine early, and FirstOrNone() follows Cheat Engine's unspecified result-list order. null and NotFound mean that the scan succeeded without a match inside the request: a factual zero of the bounded route, or a global result list without any address inside the module or range. A global scan for which Cheat Engine returns no result list is reported as IndeterminateHostResult (that route cannot tell zero matches from a host failure), never as null or NotFound.

Use the Try... terminal operations when absence of a process, scan result, or runtime capability is an expected condition. Do not make a worker wait for the Cheat Engine thread if that worker can call back into the Client.

The plugin lifecycle

The parameterless plugin instance is created by the SDK, but every enable creates new managed state:

OnEnable
  -> Configure a new builder (explicit configuration, services, modules, codecs)
  -> Build and validate a new provider and scope
  -> Resolve options and ICheatEngineClient
  -> Enable modules in registration order
  -> OnClientEnabled

OnDisable
  -> Stop admitting the active client
  -> OnClientDisabling
  -> Disable modules in reverse order
  -> Drain Client-owned CE resources while the SDK context is valid
  -> Dispose scope, provider, and configuration

ICheatEngineClient.Epoch and ICheatEngineClient.Stopping identify that activation. Never retain the client, a resource lease, a Lua reference, a cancellation token, or a target-bound value across disable/re-enable. Constructors, field initializers, and static initialization must not call Cheat Engine; the SDK binding is valid only after enable.

All Client operations are synchronous. For stateful Client operations, request validation is followed by activation admission, then caller-cancellation observation, and only then policy checks or Cheat Engine work. A stale activation therefore throws CheatEngineActivationExpiredException even when the requested capability is currently gated. A cancellation token can prevent dispatch or stop Client-managed work between steps, but it does not claim to interrupt a Lua primitive that has already started. IProcessClient.TryGetLocalProcesses is the explicit exception: it is an offline BCL catalog read, never a proof of Cheat Engine target identity, and its copied snapshots remain usable after disable. Services that touch Cheat Engine must preserve this activation-lifecycle contract.

Failures, exceptions and cancellation

Try... methods return a CheatEngineFailure for the refusals of a well-formed request, cancellation before dispatch, and Cheat Engine results that are false, absent, indeterminate or malformed; no Try... method throws a CheatEngine.SDK exception. They still throw an argument exception for a null argument, a default request, an undefined enum value or an out-of-range number (checked first, before the activation and before any Cheat Engine call), CheatEngineActivationExpiredException and CheatEngineInvalidStateException, and they rethrow exceptions from your own callbacks, codecs and Lua operations unchanged. The throwing form of the same operation throws the same failure through CheatEngineFailure.Throw(cancellationToken): a cancellation surfaces as CheatEngineOperationCanceledException, an OperationCanceledException, and every other failure as a CheatEngineClientException. Releasing a lease never throws: ICheatEngineLease.Release() returns a LeaseReleaseOutcome, and Dispose() discards it. CheatEngineFailure.HostEffect states how far the Cheat Engine primitive got: NotStarted, Started, Completed, NotApplied, CleanupUnconfirmed or Unknown. The per-family table is in the Abstractions README.

Packages and direct SDK reference

The recommended package is CheatEngine.Client. The delivery graph stays deliberately one-way:

CheatEngine.Client
├─ CheatEngine.Client.Fluent ────────────────> public contracts
└─ CheatEngine.Client.Hosting
   ├─ CheatEngine.Client.Extensions.DependencyInjection
   │  ├─ CheatEngine.Client.Core ────────────> CheatEngine.SDK
   │  └─ public contracts
   └─ CheatEngine.SDK

public contracts ────────────────────────────> stable SDK value/runtime types only
Package Purpose Consume directly when
CheatEngine.Client Umbrella package for the high-level fluent and hosting experience Building a normal plugin
CheatEngine.Client.Hosting CheatEngineClientPlugin and one-provider-per-activation host Hosting a plugin (the supported composition root)
CheatEngine.Client.Extensions.DependencyInjection Hosting's composition layer: DI registrations and options Only through Hosting in 1.0 (standalone unsupported)
CheatEngine.Client.Fluent Immutable fluent memory and AOB builders Depending only on fluent request construction
CheatEngine.Client.Abstractions Contracts, requests, failures, and value vocabulary Referencing contracts without an implementation
CheatEngine.Client.Core SDK-facing implementation Normally composed through DI, not called directly
CheatEngine.Client.Templates dotnet new ceplugin Starting a new plugin

Package and assembly names describe delivery, not user code. Consumer-facing APIs use functional namespaces such as CheatEngine.Client.Memory, .Scanning, .Tables, .Lua, .Processes, .Runtime, and .Hosting.

Capability status

The Client reports runtime capability rather than assuming a particular Cheat Engine global or ownership contract. The following table is a delivery statement, not a substitute for a live host check. Available means a stable 1.x API with an operational implementation; Experimental APIs are operational too, but carry an [Experimental("CECLIENT500x")] diagnostic and can change in a minor release until their live scenarios pass. At run time no capability reports the state Available before Client qualification receipts exist for the scenarios of its row.

Capability id Implementation 1.0 status Boundary Qualification
Client.ProcessSelection Operational adapter Available Snapshot and attachment state are re-read through the active host Unknown until Client receipts for Q30.a, Q31 and Q32 exist
Client.TypedMemory Operational adapter Available Built-in primitives (8- to 64-bit integers, float, double, Address) plus codecs passed with each request; strings and byte ranges are bounded Unknown until Client receipts for Q20, Q21 and Q33 exist
Client.PatternScanning Operational adapter Available Patterns are normalized; terminals are FirstOrNone, RequireSingle, or materialization-bounded Take; module and range scans are bounded on a qualified local target Unknown until Client receipts for Q27, Q28 and Q29 exist
Client.Inspection Operational adapter Available Modules, regions and symbols are copied; custom-symbol leases are activation-scoped Unknown until Client receipts for Q16.b and Q28 exist
Client.Tables Operational adapter Available Snapshots and hierarchy materialization are bounded; table file access requires an allowed root Unknown until Client receipts for Q34 exist
Client.ProtectedLua Operational adapter Available Typed Lua operations and explicit Lua modules; no Lua state crosses the public Client contract Unknown until Client receipts for Q05, Q16 and Q19 exist
Client.UnsafeLuaExecution Operational, policy opt-in Available with EnableUnsafeLuaExecution(), off by default Arbitrary trusted Lua source; raw Lua state remains hidden Stays Unknown: no scenario covers arbitrary Lua
Client.ValueScanning Operational adapter, experimental (CECLIENT5001) Experimental Sessions over CheatEngine.SDK's owned MemScan/FoundList: first and next scans, bounded pages of copied results Unknown until Client receipts for Q25 and Q26 exist
Client.Allocations Operational adapter, experimental (CECLIENT5002) Experimental Leases over CheatEngine.SDK's AllocatedRegion, freed only in the process that made them; executable memory needs no opt-in Unknown until Client receipts for Q30.a exist
Client.Assembly Operational adapter, experimental (CECLIENT5003) Experimental One profile-checked instruction per call, bytes read from target memory, nothing written to the target Unknown until Client receipts for Q32 exist
Client.AutoAssemblerPatches Operational, policy opt-in, experimental (CECLIENT5004) Experimental, with EnableAutoAssemblerPatches() A lease owns the disable information Cheat Engine returned Unknown until Client receipts for Q35 and Q44 exist

The activation lifecycle, main-thread dispatch, runtime facts, dependency injection, options and modules are always available. IPC, remote clients, UI/forms, structures, Mono/IL2CPP, and advanced ABI hooks remain outside 1.0 and have no placeholder public API. The capability table above is the current public-surface contract; the Abstractions README states the runtime evidence of each capability.

Not offered in 1.0

These Cheat Engine features have no public Client contract, not even a gated placeholder: timers and hotkeys; the debugger and breakpoints; the speed hack; target-memory and file hashing; DBVM; remote execution and DLL injection; pausing, resuming or creating a process, and attaching to the foreground process; assembly comments; and detaching from a process. No CheatEngine.SDK primitive backs these yet; they may arrive in a 1.x minor release once the SDK provides an owner.

CheatEngine.SDK 2.0.0 also resolves addresses in Cheat Engine's own process (EngineInspection.ResolveHostAddress) and registers symbol lists (SymbolLists). Neither is a 1.0 goal of the Client: IInspectionClient resolves in the target process only and registers one symbol per lease.

Versioning and compatibility

CheatEngine.Client follows Semantic Versioning 2.0.0 from 1.0.0. Every Client package is released with the same version; use one version for all of them.

The seven packages ship in lockstep. Each Client package depends on the Client packages it builds on at exactly its own version ([X.Y.Z] in its nuspec, not the X.Y.Z minimum NuGet writes by default), because CheatEngine.Client.Extensions.DependencyInjection and CheatEngine.Client.Hosting use internal types of CheatEngine.Client.Core and of CheatEngine.Client.Extensions.DependencyInjection, which no public API baseline protects. Reference CheatEngine.Client and let it bring the others; a Client package you reference directly takes the same version. CheatEngine.Client.Core is not a standalone package: it has no public API and is published only as a dependency of CheatEngine.Client.Extensions.DependencyInjection.

  • Patch releases (1.0.x) fix behavior and documentation without changing the public API.
  • Minor releases (1.x) add API without breaking code compiled against an earlier 1.x:
    • new types, members, overloads and namespaces;
    • new members on a call-only interface. The documentation of every public interface says whether it is Call-only (the Client implements it and applications call it, for example ICheatEngineClient, IMemoryClient or ICheatEngineLease) or Implementable (applications implement it and the Client calls it). Implement a call-only interface only in a test double, and expect to update the double in a minor release;
    • new enum values. Public enums are int enums whose explicit values never change meaning. An outcome enum (a name ending in Kind, Status, State, Effect or Scope) has Unknown = 0: handle a value you do not recognize like Unknown. An option enum has a valid default at 0 and never an outcome suffix.
  • Frozen for all of 1.x: the Implementable interfaces (ILuaModule, ILuaOperation<TResult>, ILuaResultMapper<TSource, TResult>, IMemoryCodec<T> and ICheatEngineClientModule) never gain, lose or change a member.
  • Experimental APIs, marked [Experimental("CECLIENT500x")], can change or be removed in a minor release until their live qualification passes; using one is an explicit opt-in to that diagnostic.
  • Charter: the public API charter of the CheatEngine.Client.Abstractions README fixes the Try and throwing forms, the names, the exception policy (no Client exception has a public constructor; CheatEngineFailure.Throw and ToException create them) and the CheatEngine.SDK value types a public signature may use; 1.x only adds to it.
  • Not contractual: the text of CheatEngineFailure.Message, of CheatEngineFailure.Operation and of exception messages. Classify a failure by CheatEngineFailure.Kind and HostEffect, never by text.
  • CheatEngine.SDK: Client 1.x depends on CheatEngine.SDK [2.0.0, 3.0.0). Its descriptive value types (Address, PointerSize, ModuleInfo and the others the charter lists) are part of the Client's public signatures, so a new CheatEngine.SDK major version means a new Client major version, never a Client minor release.
  • Removing or changing a stable public member, or changing the meaning of a value, happens only in a new major version.

AOT, trimming, and deployment

Shipping Client projects target net10.0, enable nullable analysis, warnings as errors, trim/AOT compatibility analysis, trim and AOT compatibility verification of every referenced assembly, deterministic builds, XML documentation, Source Link, symbol packages, and package/API validation. CheatEngine.Client.AotProbe publishes the complete Client graph as Native AOT for win-x64 to validate those library constraints.

That is not a claim that Cheat Engine can load a Native AOT plugin DLL. The supported deployment remains the framework-dependent managed plugin output folder. Deploy it as one unit: your plugin assembly, its .deps.json and .runtimeconfig.json, Client and SDK assemblies, and the SDK's cheatengine-sdk-lua-bridge.dll must remain together.

The template sets IsAotCompatible and VerifyReferenceAotCompatibility to protect the application code path, while leaving the plugin itself in the SDK-supported managed form. The package and AOT policy is enforced by the project files, package validation, and the probe described above.

Build and validation

The repository pins the .NET SDK in global.json (10.0.401 exactly), uses Central Package Management, and commits NuGet lock files. CONTRIBUTING gives the exact Windows validation sequence, from the locked restore to the package consumption tests and the Native AOT probe, and describes each job of the required CI / Gate check (Continuous integration).

The Cheat Engine 7.7 x64 live suite is opt-in and intentionally excluded from ordinary CI; no CI result should be read as proof that an untested live-host feature is available. Success, failure, cleanup, disable, re-enable, and target-change evidence for every advanced capability still requires the opt-in Cheat Engine 7.7 x64 qualification run.

See also CONTRIBUTING, RELEASING, the CHANGELOG and the LICENSE.

Security and scope

This project is for local processes you are authorized to inspect or modify. It does not add network control, remote transport, or a mechanism to bypass Cheat Engine or host protections.

Table loading can execute Lua in the host. Keep AllowedTableRoots empty unless the plugin has an explicit, trusted import/export location; an empty set disables table file access. Arbitrary Lua source is separately opt-in and should remain disabled unless the plugin has a deliberate trust boundary. Auto Assembler patches are another explicit, experimental opt-in (EnableAutoAssemblerPatches()): a script can allocate memory, inject code and run Lua, so apply only scripts the plugin owns. Avoid logging target-memory contents or Lua source by default.

Report vulnerabilities privately: see SECURITY.md.

For the SDK's bootstrap, generated Lua bindings, native bridge, and host ABI details, start with the CheatEngine.SDK README.

About

High-level, fluent, strongly typed C#/.NET client API for user-facing Cheat Engine workflows, built over CheatEngine.SDK.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages