Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ ship.

### Added

- **Source SDK builds.** Workspace consumers can opt into `CheatEngineSdkSourceRoot` to build Client and SDK together
through project references. Source builds record the SDK Engine version and Git commit from its PE metadata,
preserve plugin profile checks, and reject mismatched runtime identities. Normal NuGet builds retain their existing
package identity checks.

### Changed

- **Local process enumeration.** Ignore nonpositive operating-system process IDs, including the Windows idle
Expand Down
7 changes: 6 additions & 1 deletion Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
<Project>
<Target Name="CheatEngineClientRefuseSourceDependencyPackage" BeforeTargets="Pack;GenerateNuspec"
Condition="'$(CheatEngineSdkSourceMode)' == 'true'">
<Error Code="CHEATENGINECLIENT9051"
Text="Source SDK mode is for workspace builds. Pack Client with CheatEngineImportSourceSettings=false and no CheatEngineSdkSourceRoot, then restore the reviewed package graph."/>
</Target>
<!-- Imported after the project body and the SDK targets, so conditions here see final property values. -->

<Target Name="CheatEngineClientRequireProjectReadme"
Expand Down Expand Up @@ -69,7 +74,7 @@
<Error Condition="'$(MSBuildProjectName)' == 'CheatEngine.Client' and '$(_CheatEngineClientProjectReferences)' != 'CheatEngine.Client.Fluent;CheatEngine.Client.Hosting'"
Code="CHEATENGINECLIENT9015"
Text="CheatEngine.Client must reference only CheatEngine.Client.Fluent and CheatEngine.Client.Hosting."/>
<Error Condition="('$(MSBuildProjectName)' == 'CheatEngine.Client.Abstractions' or '$(MSBuildProjectName)' == 'CheatEngine.Client.Core' or '$(MSBuildProjectName)' == 'CheatEngine.Client.Hosting') and '@(_CheatEngineClientSdkRangeReference)' == ''"
<Error Condition="'$(CheatEngineSdkSourceMode)' != 'true' and ('$(MSBuildProjectName)' == 'CheatEngine.Client.Abstractions' or '$(MSBuildProjectName)' == 'CheatEngine.Client.Core' or '$(MSBuildProjectName)' == 'CheatEngine.Client.Hosting') and '@(_CheatEngineClientSdkRangeReference)' == ''"
Code="CHEATENGINECLIENT9016"
Text="SDK-facing published packages must declare CheatEngine.SDK as $(CheatEngineSdkVersionRange), the range of eng/CheatEngineSdk.props."/>
</Target>
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,34 @@ The aggregate `ICheatEngineClient` gives an enabled plugin access to runtime fac
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.

### Building with an SDK source checkout

NuGet remains the default. A workspace that builds Client and SDK from source can place `CheatEngine.Source.props`
beside the Client checkout with these settings:

```xml
<Project>
<PropertyGroup>
<CheatEngineSdkSourceRoot>$(MSBuildThisFileDirectory)CheatEngine.SDK</CheatEngineSdkSourceRoot>
<CheatEngineSourceLockFileDirectory>$(MSBuildThisFileDirectory)locks</CheatEngineSourceLockFileDirectory>
</PropertyGroup>
</Project>
```

The SDK root must contain its Engine and Hosting runtime projects. The optional lock directory keeps this source
graph separate from Client's committed NuGet locks. Parent-workspace settings are read during both restore and build;
`CheatEngineImportSourceSettings=false` disables the import. Command-line properties can select the same settings.
Clone complete Git history and tags so MinVer can derive the SDK assembly version and source revision.

Source consumers reference the Client facade project. A plugin also directly references the SDK Engine and Hosting
projects, imports the SDK and Client consumer build assets, and references their plugin generators as analyzers;
project references do not propagate NuGet build assets or analyzers. The SDK's checked-in native bridge remains part
of the plugin output. Source mode reads the consumed Engine PE metadata without executing it and requires that exact
version and commit at runtime. It does not claim a NuGet package hash or native qualification.

Package publication requires the normal NuGet graph; `CHEATENGINECLIENT9051` refuses source-mode packing or an invalid
SDK source root. Restore and validate the package graph before publishing Client packages.

## Why this project exists

`CheatEngine.SDK` deliberately owns the difficult boundary: the generated Cheat Engine entry point, Lua protection,
Expand Down
15 changes: 15 additions & 0 deletions eng/CheatEngineSdk.props
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<Project>
<!-- Optional parent-workspace settings allow an identical graph during recursive restore and build.
A standalone Client checkout has no such file and keeps the NuGet graph below. -->
<Import Project="$(MSBuildThisFileDirectory)../../CheatEngine.Source.props"
Condition="'$(CheatEngineImportSourceSettings)' != 'false' and Exists('$(MSBuildThisFileDirectory)../../CheatEngine.Source.props')"/>
<!--
The single reviewed source of the CheatEngine.SDK package this Client consumes (audit ADR-10, A21-01).

Expand Down Expand Up @@ -51,10 +55,21 @@
(PackageConsumptionSmokeTests.InterClientDependenciesRequireTheExactCoPackedVersion fails if it does).
-->
<PropertyGroup Label="Consumed CheatEngine.SDK">
<!-- Optional SDK checkout. Empty keeps the reviewed NuGet package graph unchanged. -->
<CheatEngineSdkSourceMode Condition="'$(CheatEngineSdkSourceRoot)' != ''">true</CheatEngineSdkSourceMode>
<_CheatEngineSdkSourceDirectory Condition="'$(CheatEngineSdkSourceMode)' == 'true'">$([MSBuild]::EnsureTrailingSlash($([System.IO.Path]::GetFullPath('$(CheatEngineSdkSourceRoot)'))))</_CheatEngineSdkSourceDirectory>
<CheatEngineSdkEngineProject Condition="'$(CheatEngineSdkSourceMode)' == 'true'">$(_CheatEngineSdkSourceDirectory)libs/CheatEngine.SDK.Engine/CheatEngine.SDK.Engine.csproj</CheatEngineSdkEngineProject>
<CheatEngineSdkHostingProject Condition="'$(CheatEngineSdkSourceMode)' == 'true'">$(_CheatEngineSdkSourceDirectory)libs/CheatEngine.SDK.Hosting/CheatEngine.SDK.Hosting.csproj</CheatEngineSdkHostingProject>
<NuGetLockFilePath Condition="'$(CheatEngineSourceLockFileDirectory)' != ''">$(CheatEngineSourceLockFileDirectory)/$(MSBuildProjectName).lock.json</NuGetLockFilePath>
<CheatEngineSdkVersion>2.0.0</CheatEngineSdkVersion>
<CheatEngineSdkUpperBound>3.0.0</CheatEngineSdkUpperBound>
<CheatEngineSdkVersionRange>[$(CheatEngineSdkVersion),$(CheatEngineSdkUpperBound))</CheatEngineSdkVersionRange>
<!-- The one SDK major this Client supports. Changing it is a major migration (see above), not a dependency bump. -->
<_CheatEngineClientSupportedSdkMajor>2</_CheatEngineClientSupportedSdkMajor>
</PropertyGroup>
<Target Name="CheatEngineClientValidateSdkSourceRoot" BeforeTargets="CollectPackageReferences"
Condition="'$(CheatEngineSdkSourceMode)' == 'true'">
<Error Condition="!Exists('$(CheatEngineSdkEngineProject)') or !Exists('$(CheatEngineSdkHostingProject)')" Code="CHEATENGINECLIENT9051"
Text="CheatEngineSdkSourceRoot must name an SDK checkout containing the Engine and Hosting runtime projects."/>
</Target>
</Project>
80 changes: 80 additions & 0 deletions eng/SourceSdkIdentity.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<Project>
<!-- Read ECMA-335 metadata only. The SDK is not loaded into the MSBuild process. -->
<UsingTask TaskName="CheatEngineClientReadSourceSdkMetadata" TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)/Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<AssemblyPath ParameterType="System.String" Required="true"/>
<SupportedMajor ParameterType="System.String" Required="true"/>
<SdkVersion ParameterType="System.String" Output="true"/>
<SourceCommit ParameterType="System.String" Output="true"/>
</ParameterGroup>
<Task>
<Reference Include="$(MSBuildToolsPath)/Sdks/Microsoft.NET.Sdk/tools/net472/System.Collections.Immutable.dll"/>
<Reference Include="$(MSBuildToolsPath)/Sdks/Microsoft.NET.Sdk/tools/net472/System.Reflection.Metadata.dll"/>
<Using Namespace="System"/>
<Using Namespace="System.IO"/>
<Using Namespace="System.Reflection.Metadata"/>
<Using Namespace="System.Reflection.PortableExecutable"/>
<Using Namespace="System.Text.RegularExpressions"/>
<Code Type="Fragment" Language="cs"><![CDATA[
try
{
using (var stream = File.OpenRead(AssemblyPath))
using (var pe = new PEReader(stream))
{
var reader = pe.GetMetadataReader();
var assembly = reader.GetAssemblyDefinition();
if (reader.GetString(assembly.Name) != "CheatEngine.SDK.Engine")
throw new InvalidDataException("The resolved source assembly is not CheatEngine.SDK.Engine.");
string information = null;
foreach (var handle in assembly.GetCustomAttributes())
{
var attribute = reader.GetCustomAttribute(handle);
if (attribute.Constructor.Kind != HandleKind.MemberReference) continue;
var constructor = reader.GetMemberReference((MemberReferenceHandle)attribute.Constructor);
if (constructor.Parent.Kind != HandleKind.TypeReference) continue;
var type = reader.GetTypeReference((TypeReferenceHandle)constructor.Parent);
if (reader.GetString(type.Namespace) != "System.Reflection" ||
reader.GetString(type.Name) != "AssemblyInformationalVersionAttribute") continue;
if (information != null) throw new InvalidDataException("Duplicate SDK informational version.");
var blob = reader.GetBlobReader(attribute.Value);
if (blob.ReadUInt16() != 1) throw new InvalidDataException("Invalid SDK version attribute.");
information = blob.ReadSerializedString();
if (blob.RemainingBytes != 2 || blob.ReadUInt16() != 0)
throw new InvalidDataException("Invalid SDK version attribute arguments.");
}
var identity = Regex.Match(information ?? "", @"^(?<version>(?<major>[0-9]+)\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?)\+(?<commit>[0-9a-f]{40})$");
if (!identity.Success || identity.Groups["major"].Value != SupportedMajor)
throw new InvalidDataException("The source SDK must declare its supported major, semantic version and full Git commit in AssemblyInformationalVersion.");
SdkVersion = identity.Groups["version"].Value;
SourceCommit = identity.Groups["commit"].Value;
}
}
catch (Exception exception)
{
Log.LogError(null, "CHEATENGINECLIENT9050", null, AssemblyPath, 0, 0, 0, 0,
"Cannot establish the consumed source SDK identity: {0}", exception.Message);
return false;
}
]]></Code>
</Task>
</UsingTask>
<Target Name="CheatEngineClientEmbedSourceSdkIdentity" BeforeTargets="GetAssemblyAttributes"
DependsOnTargets="ResolveReferences" Condition="'$(DesignTimeBuild)' != 'true'">
<ItemGroup>
<_CheatEngineClientSourceEngine Include="@(ReferenceCopyLocalPaths)"
Condition="'%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)' == 'CheatEngine.SDK.Engine.dll'"/>
</ItemGroup>
<CheatEngineClientReadSourceSdkMetadata AssemblyPath="@(_CheatEngineClientSourceEngine->Distinct())"
SupportedMajor="$(_CheatEngineClientSupportedSdkMajor)">
<Output TaskParameter="SdkVersion" PropertyName="_CheatEngineClientSourceSdkVersion"/>
<Output TaskParameter="SourceCommit" PropertyName="_CheatEngineClientSourceSdkCommit"/>
</CheatEngineClientReadSourceSdkMetadata>
<ItemGroup>
<AssemblyMetadata Include="CheatEngine.Client.ConsumedSdk.Kind" Value="source"/>
<AssemblyMetadata Include="CheatEngine.Client.ConsumedSdk.Version" Value="$(_CheatEngineClientSourceSdkVersion)"/>
<AssemblyMetadata Include="CheatEngine.Client.ConsumedSdk.SourceCommit" Value="$(_CheatEngineClientSourceSdkCommit)"/>
<AssemblyMetadata Include="CheatEngine.Client.ConsumedSdk.SupportedMajor" Value="$(_CheatEngineClientSupportedSdkMajor)"/>
</ItemGroup>
</Target>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CheatEngine.SDK"
<ProjectReference Include="$(CheatEngineSdkEngineProject)" Condition="'$(CheatEngineSdkSourceMode)' == 'true'"/>
<ProjectReference Include="$(CheatEngineSdkHostingProject)" Condition="'$(CheatEngineSdkSourceMode)' == 'true'"/>
<PackageReference Include="CheatEngine.SDK" Condition="'$(CheatEngineSdkSourceMode)' != 'true'"
VersionOverride="$(CheatEngineSdkVersionRange)"
IncludeAssets="compile;runtime"
ExcludeAssets="build;buildTransitive;analyzers;native;contentFiles"/>
Expand Down
12 changes: 8 additions & 4 deletions libs/CheatEngine.Client.Core/CheatEngine.Client.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@

<ItemGroup>
<ProjectReference Include="../CheatEngine.Client.Abstractions/CheatEngine.Client.Abstractions.csproj"/>
<PackageReference Include="CheatEngine.SDK" VersionOverride="$(CheatEngineSdkVersionRange)"/>
<ProjectReference Include="$(CheatEngineSdkEngineProject)" Condition="'$(CheatEngineSdkSourceMode)' == 'true'"/>
<ProjectReference Include="$(CheatEngineSdkHostingProject)" Condition="'$(CheatEngineSdkSourceMode)' == 'true'"/>
<PackageReference Include="CheatEngine.SDK" Condition="'$(CheatEngineSdkSourceMode)' != 'true'" VersionOverride="$(CheatEngineSdkVersionRange)"/>
</ItemGroup>

<ItemGroup>
Expand All @@ -38,7 +40,7 @@
Property functions: https://learn.microsoft.com/visualstudio/msbuild/property-functions
AssemblyMetadata items: https://learn.microsoft.com/dotnet/core/project-sdk/msbuild-props#assemblymetadata
-->
<PropertyGroup Label="Consumed CheatEngine.SDK identity">
<PropertyGroup Label="Consumed CheatEngine.SDK identity" Condition="'$(CheatEngineSdkSourceMode)' != 'true'">
<_CheatEngineClientSdkLockFile>$(MSBuildThisFileDirectory)packages.lock.json</_CheatEngineClientSdkLockFile>
<_CheatEngineClientSdkLockJson Condition="Exists('$(_CheatEngineClientSdkLockFile)')">$([System.IO.File]::ReadAllText('$(_CheatEngineClientSdkLockFile)'))</_CheatEngineClientSdkLockJson>
<_CheatEngineClientLockedSdkVersion Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('$(_CheatEngineClientSdkLockJson)', '&quot;CheatEngine\.SDK&quot;\s*:\s*\{[^}]*?&quot;resolved&quot;\s*:\s*&quot;[0-9A-Za-z.+-]+&quot;'))">$([System.Text.RegularExpressions.Regex]::Replace('$(_CheatEngineClientSdkLockJson)', '(?s)^.*?&quot;CheatEngine\.SDK&quot;\s*:\s*\{[^}]*?&quot;resolved&quot;\s*:\s*&quot;([0-9A-Za-z.+-]+)&quot;.*$', '$1'))</_CheatEngineClientLockedSdkVersion>
Expand All @@ -59,7 +61,7 @@
<_CheatEngineClientEmbedConsumedSdk Condition="'$(_CheatEngineClientConsumedSdkProblem)' == ''">true</_CheatEngineClientEmbedConsumedSdk>
</PropertyGroup>

<ItemGroup Label="Consumed CheatEngine.SDK identity" Condition="'$(_CheatEngineClientEmbedConsumedSdk)' == 'true'">
<ItemGroup Label="Consumed CheatEngine.SDK identity" Condition="'$(CheatEngineSdkSourceMode)' != 'true' and '$(_CheatEngineClientEmbedConsumedSdk)' == 'true'">
<AssemblyMetadata Include="CheatEngine.Client.ConsumedSdk.Version" Value="$(_CheatEngineClientLockedSdkVersion)"/>
<AssemblyMetadata Include="CheatEngine.Client.ConsumedSdk.SourceCommit" Value="$(_CheatEngineClientRestoredSdkCommit)"/>
<AssemblyMetadata Include="CheatEngine.Client.ConsumedSdk.ContentHashSha512" Value="$(_CheatEngineClientLockedSdkContentHash)"/>
Expand All @@ -71,9 +73,11 @@
producing a Core assembly whose package gates would all report Unknown. Design-time builds are exempt.
-->
<Target Name="CheatEngineClientRequireConsumedSdkIdentity" BeforeTargets="CoreCompile"
Condition="'$(DesignTimeBuild)' != 'true'">
Condition="'$(DesignTimeBuild)' != 'true' and '$(CheatEngineSdkSourceMode)' != 'true'">
<Error Code="CHEATENGINECLIENT9050" Condition="'$(_CheatEngineClientEmbedConsumedSdk)' != 'true'"
Text="CheatEngine.Client.Core cannot embed the identity of the consumed CheatEngine.SDK package: $(_CheatEngineClientConsumedSdkProblem). Without it the runtime package gate of every operational capability would report Unknown. Restore with the committed lock files (dotnet restore --locked-mode) and keep eng/CheatEngineSdk.props, libs/CheatEngine.Client.Core/packages.lock.json and the restored package in agreement."/>
</Target>

<Import Project="../../eng/SourceSdkIdentity.targets" Condition="'$(CheatEngineSdkSourceMode)' == 'true'"/>

</Project>
Loading
Loading