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
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public LuaStatus LuaStatus
public static ProcessOperationStatus GlobalUnavailable =>
new(ProcessOperationStatusKind.GlobalUnavailable, LuaStatus.Ok);

/// <summary>Gets a status for a result outside the documented process-observation shape.</summary>
/// <summary>Gets a status for a result outside the documented process selection or observation shape.</summary>
public static ProcessOperationStatus InvalidResult => new(ProcessOperationStatusKind.InvalidResult, LuaStatus.Ok);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ public enum ProcessOperationStatusKind
TargetNotAttached = 2,

/// <summary>
/// An explicit selection call returned normally, but the immediately observed process was absent or did not match
/// the requested identifier.
/// An explicit selection returned no valid-handle confirmation (nil or false), or its immediately observed
/// process was absent or did not match the requested identifier.
/// </summary>
SelectionNotConfirmed = 3,

Expand All @@ -28,7 +28,7 @@ public enum ProcessOperationStatusKind
/// <summary>A protected Lua lookup or invocation failed.</summary>
ProtectedLuaFailure = 5,

/// <summary>The host returned a value outside the documented process-observation shape.</summary>
/// <summary>The host returned a value outside the documented process selection or observation shape.</summary>
InvalidResult = 6,

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@
/// <param name="processId">The positive process identifier to select.</param>
/// <param name="observation">The copied matching target observation only when the returned status is successful.</param>
/// <returns>
/// The factual protected selection status. A normal <c>openProcess</c> return is not success by itself: success
/// requires the next <c>getOpenedProcessID</c> observation to equal <paramref name="processId" />.
/// The factual protected selection status. Success requires <c>openProcess</c> to return boolean
/// <see langword="true" />, confirming CE's nonzero process handle, and the next <c>getOpenedProcessID</c>
/// observation to equal <paramref name="processId" />. A missing or false confirmation refuses selection.
/// </returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="processId" /> is default or otherwise non-positive.</exception>
/// <exception cref="InvalidOperationException">The plugin is not enabled or the calling thread has no Lua state.</exception>
Expand Down Expand Up @@ -304,10 +305,26 @@
}

state.PushInteger(processId.Value);
LuaStatus luaStatus = state.TryCall(1, 0);
return luaStatus.IsOk
LuaStatus luaStatus = state.TryCall(1, 1);
if (!luaStatus.IsOk)
{
return ProcessOperationStatus.ProtectedLuaFailure(luaStatus);
}

// CE assigns the requested PID before opening its handle. Only its true return confirms both.
LuaType resultType = state.TypeOf(-1);
if (resultType == LuaType.Nil)
{
return ProcessOperationStatus.SelectionNotConfirmed;
}
if (resultType != LuaType.Boolean)

Check warning on line 320 in libs/CheatEngine.SDK.Engine/Processes/RuntimeProcessOperations.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'if' statement can be simplified

See more on https://sonarcloud.io/project/issues?id=CheatEngineNet_CheatEngine.SDK&issues=AaFVHSX9-J0B8xuc_qKH&open=AaFVHSX9-J0B8xuc_qKH&pullRequest=98
{
return ProcessOperationStatus.InvalidResult;
}

return state.ToBoolean(-1)
? ProcessOperationStatus.Success
: ProcessOperationStatus.ProtectedLuaFailure(luaStatus);
: ProcessOperationStatus.SelectionNotConfirmed;
}

private static ProcessOperationStatus TryCallBoolean(LuaState state, LuaRef cache, ReadOnlySpan<byte> globalName,
Expand Down
3 changes: 2 additions & 1 deletion libs/CheatEngine.SDK.Engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,8 @@ observations `RuntimeProcessOperations.ObserveTargetArchitecture` and `TryGetCon
`RuntimeObservations.TryObserveRuntimeInfo` and `InstructionProfiles.TryObserveCurrent` also read it last and report a
difference as `TargetChanged`; instruction operations re-check it before and after their Cheat Engine call.
`TargetSelection.ObserveCurrent` and `RuntimeProcessOperations.ObserveCurrent` read it once, before their other probes,
and `RuntimeProcessOperations.SelectAndObserve` reads it once, after `openProcess`, to confirm the selection.
and `RuntimeProcessOperations.SelectAndObserve` first requires `openProcess` to return boolean `true`, confirming
CE's nonzero process handle, then reads the PID once to confirm the selection. A reported PID alone is insufficient.

| Fact | CE global | SDK member | When unknown |
|-----------------------------------|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ public void SelectAndObserve_explicit_pid_requires_the_next_ce_observation_to_ma
LuaState lua = scope.State;
EngineTest.Run(lua, """
opened = 0
function openProcess(id) opened = id end
function openProcess(id) opened = id return true end
function getOpenedProcessID() return opened end
function targetIs64Bit() return true end
"""u8);
Expand All @@ -274,7 +274,7 @@ public void SelectAndObserve_unconfirmed_or_failing_selection_keeps_outcomes_dis
{
LuaState lua = mismatchScope.State;
EngineTest.Run(lua, """
function openProcess(_) end
function openProcess(_) return true end
function getOpenedProcessID() return 77 end
function targetIs64Bit() error('must not be called after a mismatched PID') end
"""u8);
Expand All @@ -298,6 +298,37 @@ function targetIs64Bit() error('must not be called after a mismatched PID') end
Assert.Equal(0, failureScope.State.Top);
}

[Theory]
[InlineData("", ProcessOperationStatusKind.SelectionNotConfirmed)]
[InlineData("return false", ProcessOperationStatusKind.SelectionNotConfirmed)]
[InlineData("return 'true'", ProcessOperationStatusKind.InvalidResult)]
[InlineData("return 1", ProcessOperationStatusKind.InvalidResult)]
[InlineData("return {}", ProcessOperationStatusKind.InvalidResult)]
public void SelectAndObserve_rejects_an_unconfirmed_or_malformed_open_process_result(
string returned, ProcessOperationStatusKind expected)
{
EngineTest.RequireNativeLua();
using NativeLuaState state = new();
using HostScope scope = new(state);
LuaState lua = scope.State;
EngineTest.Run(lua, Encoding.UTF8.GetBytes($$"""
pidReads = 0
bitnessReads = 0
function openProcess(id) opened = id {{returned}} end
function getOpenedProcessID() pidReads = pidReads + 1 return opened end
function targetIs64Bit() bitnessReads = bitnessReads + 1 return true end
"""));

ProcessOperationStatus status = RuntimeProcessOperations.SelectAndObserve(new TargetProcessId(42),
out CurrentProcessObservation observation);

Assert.Equal(expected, status.Kind);
Assert.Equal(default, observation);
Assert.Equal(0, lua.Top);
EngineTest.Run(lua, "assert(pidReads == 0 and bitnessReads == 0, 'unconfirmed selection was observed')"u8);
Assert.Equal(0, lua.Top);
}

[Fact]
public void SelectAndObserve_rejects_a_default_process_identifier_before_entering_the_lua_runtime()
{
Expand Down
Loading