feat(core): port decompiled AC client physics — CollisionPrimitives + PhysicsBody
Two major C# ports from the decompiled retail AC client (acclient.exe): 1. CollisionPrimitives (9 functions, 26 tests): - SphereIntersectsRay, RayPlaneIntersect, CalcNormal - SphereIntersectsPoly, FindTimeOfCollision - HitsWalkable, FindWalkableCollision - SlideSphere, LandOnSphere Ported from chunk_00530000.c functions FUN_005384e0 through FUN_0053a230. Cross-referenced against ACE's Physics/ C# port for algorithm verification. 2. PhysicsBody (7 methods, 31 tests): - update_object (top-level per-frame, sub-stepped at MaxQuantum=0.1) - UpdatePhysicsInternal (Euler: pos += v*dt + 0.5*a*dt²) - calc_acceleration (gravity=-9.8 when HasGravity) - set_velocity (clamp to MaxVelocity=50) - set_local_velocity (body→world via quaternion) - set_on_walkable, calc_friction (ground normal + pow decay) Ported from chunk_00510000.c/chunk_00500000.c. Struct layout confirmed against ACE PhysicsObj field offsets. 367 total tests green (258 core + 109 net). 57 new tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
21fd550909
commit
6a5d8c1580
36 changed files with 989 additions and 0 deletions
373
src/AcDream.Core/Physics/PhysicsBody.cs
Normal file
373
src/AcDream.Core/Physics/PhysicsBody.cs
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace AcDream.Core.Physics;
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// PhysicsBody — C# port of CPhysicsObj's core simulation from acclient.exe.
|
||||
//
|
||||
// Source addresses (chunk_00510000.c, chunk_00500000.c):
|
||||
// FUN_005111d0 UpdatePhysicsInternal — Euler integration
|
||||
// FUN_00511420 calc_acceleration — gravity / grounded acceleration
|
||||
// FUN_00511ec0 set_velocity — store + clamp to MaxVelocity
|
||||
// FUN_00511fa0 set_local_velocity — body→world transform then set_velocity
|
||||
// FUN_00511de0 set_on_walkable — set/clear OnWalkable transient flag
|
||||
// FUN_0050f940 calc_friction — ground-contact friction
|
||||
// FUN_00515020 update_object — per-frame top-level driver
|
||||
//
|
||||
// Cross-checked against ACE PhysicsObj.cs and PhysicsGlobals.cs.
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// State flags stored at struct offset +0xA8 (PhysicsState).
|
||||
/// Only the flags relevant to this simulation layer are included.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum PhysicsStateFlags : uint
|
||||
{
|
||||
None = 0,
|
||||
Static = 0x00000001, // bit 0 — never moves
|
||||
Ethereal = 0x00000004, // bit 2 — no collision
|
||||
ReportCollisions = 0x00000010,
|
||||
Gravity = 0x00000400, // bit 10 — apply downward gravity
|
||||
Hidden = 0x00001000,
|
||||
Sledding = 0x00800000, // bit 23 — sledding (modified friction)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transient-state flags stored at struct offset +0xAC (TransientState).
|
||||
/// These are cleared/set each frame and must not be saved to disk.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum TransientStateFlags : uint
|
||||
{
|
||||
None = 0,
|
||||
Contact = 0x00000001, // bit 0 — touching any surface
|
||||
OnWalkable = 0x00000002, // bit 1 — standing on a walkable surface
|
||||
Active = 0x00000080, // bit 7 — object needs per-frame update
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Port of CPhysicsObj's core simulation state and Euler integration.
|
||||
/// Holds the fields at the struct offsets documented in acclient_function_map.md
|
||||
/// and implements the seven methods listed in the task spec.
|
||||
/// </summary>
|
||||
public sealed class PhysicsBody
|
||||
{
|
||||
// ── constants ──────────────────────────────────────────────────────────
|
||||
// From PhysicsGlobals.cs / confirmed by DAT_007c78a4 reference in decompiled code.
|
||||
public const float MaxVelocity = 50.0f;
|
||||
public const float MaxVelocitySquared = MaxVelocity * MaxVelocity;
|
||||
public const float Gravity = -9.8f; // DAT_0082223c in FUN_00511420
|
||||
public const float SmallVelocity = 0.25f;
|
||||
public const float SmallVelocitySquared = SmallVelocity * SmallVelocity;
|
||||
public const float DefaultFriction = 0.95f;
|
||||
public const float MinQuantum = 1.0f / 30.0f; // ~0.0333 s
|
||||
public const float MaxQuantum = 0.1f; // 10 fps lower bound
|
||||
public const float HugeQuantum = 2.0f; // discard stale dt
|
||||
|
||||
// ── struct fields ──────────────────────────────────────────────────────
|
||||
// Offsets from acclient_function_map.md §PhysicsObj Struct Layout.
|
||||
|
||||
/// <summary>World-space position (no offset in struct — frame origin).</summary>
|
||||
public Vector3 Position { get; set; }
|
||||
|
||||
/// <summary>Orientation quaternion (struct offsets 0x60–0x80 column matrix).</summary>
|
||||
public Quaternion Orientation { get; set; } = Quaternion.Identity;
|
||||
|
||||
/// <summary>World-space velocity (+0xE0/E4/E8).</summary>
|
||||
public Vector3 Velocity { get; set; }
|
||||
|
||||
/// <summary>World-space acceleration (+0xEC/F0/F4).</summary>
|
||||
public Vector3 Acceleration { get; set; }
|
||||
|
||||
/// <summary>Angular velocity in radians/s (+0xF8/FC/100).</summary>
|
||||
public Vector3 Omega { get; set; }
|
||||
|
||||
/// <summary>Ground contact-plane normal (+0x130/134/138).</summary>
|
||||
public Vector3 GroundNormal { get; set; } = Vector3.UnitZ;
|
||||
|
||||
/// <summary>Elasticity coefficient (+0xB0).</summary>
|
||||
public float Elasticity { get; set; } = 0.05f;
|
||||
|
||||
/// <summary>Friction coefficient (0 = frictionless, 1 = instant stop).</summary>
|
||||
public float Friction { get; set; } = DefaultFriction;
|
||||
|
||||
/// <summary>Physics state flags (+0xA8).</summary>
|
||||
public PhysicsStateFlags State { get; set; }
|
||||
= PhysicsStateFlags.Gravity | PhysicsStateFlags.ReportCollisions;
|
||||
|
||||
/// <summary>Transient state flags (+0xAC). Cleared each frame as needed.</summary>
|
||||
public TransientStateFlags TransientState { get; set; }
|
||||
|
||||
/// <summary>Last simulation time used to compute dt (+0xD8).</summary>
|
||||
public double LastUpdateTime { get; set; }
|
||||
|
||||
// ── convenience helpers ────────────────────────────────────────────────
|
||||
|
||||
public bool HasGravity => State.HasFlag(PhysicsStateFlags.Gravity);
|
||||
public bool OnWalkable => TransientState.HasFlag(TransientStateFlags.OnWalkable);
|
||||
public bool IsActive => TransientState.HasFlag(TransientStateFlags.Active);
|
||||
public bool InContact => TransientState.HasFlag(TransientStateFlags.Contact);
|
||||
|
||||
// ── FUN_00511420 ───────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Set Acceleration (and Omega) based on current contact state and flags.
|
||||
///
|
||||
/// Decompiled logic (FUN_00511420):
|
||||
/// If Contact AND OnWalkable AND NOT Sledding → zero everything (grounded, no drift).
|
||||
/// Else if Gravity flag → Accel = (0, 0, -9.8).
|
||||
/// Else → zero acceleration.
|
||||
///
|
||||
/// The check order in the decompile is:
|
||||
/// (TransientState & 1) != 0 → Contact
|
||||
/// (TransientState & 2) != 0 → OnWalkable
|
||||
/// (State & 0x800000) == 0 → NOT Sledding
|
||||
/// </summary>
|
||||
public void calc_acceleration()
|
||||
{
|
||||
if (TransientState.HasFlag(TransientStateFlags.Contact) &&
|
||||
TransientState.HasFlag(TransientStateFlags.OnWalkable) &&
|
||||
!State.HasFlag(PhysicsStateFlags.Sledding))
|
||||
{
|
||||
Acceleration = Vector3.Zero;
|
||||
Omega = Vector3.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
if (State.HasFlag(PhysicsStateFlags.Gravity))
|
||||
Acceleration = new Vector3(0f, 0f, Gravity);
|
||||
else
|
||||
Acceleration = Vector3.Zero;
|
||||
}
|
||||
|
||||
// ── FUN_00511ec0 ───────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Store a new world-space velocity and clamp its magnitude to MaxVelocity.
|
||||
///
|
||||
/// Decompiled logic (FUN_00511ec0):
|
||||
/// velocity = newVelocity
|
||||
/// if |velocity|² > MaxVelocity²:
|
||||
/// normalize then scale by MaxVelocity (FUN_00452440 = normalize + scalar)
|
||||
/// Set Active transient flag.
|
||||
/// </summary>
|
||||
public void set_velocity(Vector3 newVelocity)
|
||||
{
|
||||
Velocity = newVelocity;
|
||||
|
||||
float mag2 = Velocity.LengthSquared();
|
||||
if (mag2 > MaxVelocitySquared)
|
||||
{
|
||||
// Normalize then scale — matches the decompile's FUN_00452440 call
|
||||
// which normalizes the vector then multiplies by _DAT_007c78a4 (MaxVelocity).
|
||||
Velocity = Vector3.Normalize(Velocity) * MaxVelocity;
|
||||
}
|
||||
|
||||
// Set Active flag (bit 7 of TransientState, offset +0xAC).
|
||||
TransientState |= TransientStateFlags.Active;
|
||||
}
|
||||
|
||||
// ── FUN_00511fa0 ───────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Transform a body-local velocity vector into world space using the
|
||||
/// orientation quaternion, then call set_velocity.
|
||||
///
|
||||
/// Decompiled logic (FUN_00511fa0):
|
||||
/// The orientation is stored as a 3x3 column matrix at offsets 0x60–0x80
|
||||
/// (9 floats). The transform is a straightforward matrix×vector multiply:
|
||||
/// worldX = col0.x*localX + col1.x*localY + col2.x*localZ
|
||||
/// worldY = col0.y*localX + col1.y*localY + col2.y*localZ
|
||||
/// worldZ = col0.z*localX + col1.z*localY + col2.z*localZ
|
||||
/// We replicate this as a Quaternion rotation, which is equivalent.
|
||||
/// </summary>
|
||||
public void set_local_velocity(Vector3 localVelocity)
|
||||
{
|
||||
var worldVelocity = Vector3.Transform(localVelocity, Orientation);
|
||||
set_velocity(worldVelocity);
|
||||
}
|
||||
|
||||
// ── FUN_00511de0 ───────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Set or clear the OnWalkable transient flag (bit 1 of TransientState at
|
||||
/// +0xAC), then recompute acceleration.
|
||||
///
|
||||
/// Decompiled logic (FUN_00511de0):
|
||||
/// if param_2 == 0: TransientState &= ~0x02 (clear OnWalkable)
|
||||
/// else: TransientState |= 0x02 (set OnWalkable)
|
||||
/// call calc_acceleration()
|
||||
/// </summary>
|
||||
public void set_on_walkable(bool isOnWalkable)
|
||||
{
|
||||
if (isOnWalkable)
|
||||
TransientState |= TransientStateFlags.OnWalkable;
|
||||
else
|
||||
TransientState &= ~TransientStateFlags.OnWalkable;
|
||||
|
||||
calc_acceleration();
|
||||
}
|
||||
|
||||
// ── FUN_0050f940 ───────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Apply friction deceleration to the velocity when the body is standing
|
||||
/// on a walkable surface.
|
||||
///
|
||||
/// Decompiled logic (FUN_0050f940):
|
||||
/// if NOT OnWalkable → return
|
||||
/// fVar1 = dot(groundNormal, velocity)
|
||||
/// if fVar1 < 0:
|
||||
/// velocity -= fVar1 * groundNormal (remove inward normal component)
|
||||
/// scalar = pow(1 - friction, dt)
|
||||
/// velocity *= scalar
|
||||
///
|
||||
/// The threshold (0.0 from _DAT_007c78a0) means any velocity with a
|
||||
/// downward component relative to the normal gets friction applied.
|
||||
/// Positive dot means moving away from the surface — no friction.
|
||||
///
|
||||
/// Cross-checked with ACE PhysicsObj.calc_friction which uses 0.25f as
|
||||
/// the threshold instead; the decompile uses 0.0. We match the decompile.
|
||||
/// </summary>
|
||||
public void calc_friction(float dt, float velocityMag2)
|
||||
{
|
||||
if (!TransientState.HasFlag(TransientStateFlags.OnWalkable))
|
||||
return;
|
||||
|
||||
float dot = Vector3.Dot(GroundNormal, Velocity);
|
||||
if (dot >= 0f)
|
||||
return;
|
||||
|
||||
// Remove the component of velocity that presses into the ground normal.
|
||||
Velocity -= dot * GroundNormal;
|
||||
|
||||
float friction = Friction;
|
||||
|
||||
// Sledding modifies friction thresholds (from ACE cross-check).
|
||||
if (State.HasFlag(PhysicsStateFlags.Sledding))
|
||||
{
|
||||
if (velocityMag2 < 1.5625f) // 1.25² — slow sled
|
||||
friction = 1.0f;
|
||||
else if (velocityMag2 >= 6.25f && GroundNormal.Z > 0.99999536f) // near-flat
|
||||
friction = 0.2f;
|
||||
}
|
||||
|
||||
// Exponential decay: vel *= (1 - friction)^dt
|
||||
float scalar = MathF.Pow(1.0f - friction, dt);
|
||||
Velocity *= scalar;
|
||||
}
|
||||
|
||||
// ── FUN_005111d0 ───────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Euler integration step for one quantum dt.
|
||||
///
|
||||
/// Decompiled logic (FUN_005111d0):
|
||||
/// velocity_mag2 = |velocity|²
|
||||
/// if velocity_mag2 == 0:
|
||||
/// if no MovementManager AND OnWalkable → clear Active flag
|
||||
/// else:
|
||||
/// if velocity_mag2 > MaxVelocitySquared: normalize * MaxVelocity
|
||||
/// calc_friction(dt, velocity_mag2)
|
||||
/// if velocity_mag2 < SmallVelocitySquared: zero velocity
|
||||
/// position += velocity * dt + 0.5 * acceleration * dt²
|
||||
/// velocity += acceleration * dt
|
||||
/// Apply angular delta: orientation rotated by omega * dt
|
||||
/// </summary>
|
||||
public void UpdatePhysicsInternal(float dt)
|
||||
{
|
||||
float velocityMag2 = Velocity.LengthSquared();
|
||||
|
||||
if (velocityMag2 <= 0f)
|
||||
{
|
||||
// No movement manager equivalent here; just clear Active if grounded.
|
||||
if (TransientState.HasFlag(TransientStateFlags.OnWalkable))
|
||||
TransientState &= ~TransientStateFlags.Active;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clamp velocity magnitude to MaxVelocity.
|
||||
if (velocityMag2 > MaxVelocitySquared)
|
||||
{
|
||||
Velocity = Vector3.Normalize(Velocity) * MaxVelocity;
|
||||
velocityMag2 = MaxVelocitySquared;
|
||||
}
|
||||
|
||||
calc_friction(dt, velocityMag2);
|
||||
|
||||
// If velocity fell below the "small" threshold after friction, stop.
|
||||
if (velocityMag2 - SmallVelocitySquared < 0.0002f)
|
||||
Velocity = Vector3.Zero;
|
||||
|
||||
// Euler integration: position += v*dt + 0.5*a*dt²
|
||||
Position += Velocity * dt + Acceleration * (0.5f * dt * dt);
|
||||
}
|
||||
|
||||
// velocity += acceleration * dt (done unconditionally in decompile)
|
||||
Velocity += Acceleration * dt;
|
||||
|
||||
// Angular integration: apply omega rotation.
|
||||
// omega * dt gives the angle-axis delta rotation.
|
||||
float omegaLen = Omega.Length();
|
||||
if (omegaLen > 1e-6f)
|
||||
{
|
||||
float angle = omegaLen * dt;
|
||||
Quaternion deltaRot = Quaternion.CreateFromAxisAngle(Omega / omegaLen, angle);
|
||||
Orientation = Quaternion.Normalize(Quaternion.Multiply(Orientation, deltaRot));
|
||||
}
|
||||
}
|
||||
|
||||
// ── FUN_00515020 ───────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Per-frame top-level driver. Computes dt from the wall clock versus
|
||||
/// LastUpdateTime, clamping to [MinQuantum, HugeQuantum], then calls
|
||||
/// calc_acceleration and UpdatePhysicsInternal.
|
||||
///
|
||||
/// Decompiled logic (FUN_00515020):
|
||||
/// if parent-attached (offset +0x40 != 0) → return
|
||||
/// dVar1 = currentTime - LastUpdateTime
|
||||
/// if dVar1 < MinQuantum → return (too short — skip)
|
||||
/// if dVar1 > HugeQuantum → update timestamp and return (stale — discard)
|
||||
/// while dVar1 > MaxQuantum: simulate MaxQuantum step, subtract
|
||||
/// if dVar1 > MinQuantum: simulate remainder
|
||||
/// LastUpdateTime = currentTime
|
||||
///
|
||||
/// The caller passes currentTime; the object does not read a global clock
|
||||
/// directly in this port so tests can drive the clock explicitly.
|
||||
/// </summary>
|
||||
public void update_object(double currentTime)
|
||||
{
|
||||
double deltaTime = currentTime - LastUpdateTime;
|
||||
|
||||
// dt too small — nothing to simulate yet
|
||||
if (deltaTime < MinQuantum)
|
||||
return;
|
||||
|
||||
// Stale / first frame — just consume the time without simulating
|
||||
if (deltaTime > HugeQuantum)
|
||||
{
|
||||
LastUpdateTime = currentTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// Sub-step: break large dt into MaxQuantum chunks
|
||||
while (deltaTime > MaxQuantum)
|
||||
{
|
||||
calc_acceleration();
|
||||
UpdatePhysicsInternal(MaxQuantum);
|
||||
deltaTime -= MaxQuantum;
|
||||
}
|
||||
|
||||
// Simulate the remainder
|
||||
if (deltaTime > MinQuantum)
|
||||
{
|
||||
calc_acceleration();
|
||||
UpdatePhysicsInternal((float)deltaTime);
|
||||
}
|
||||
|
||||
LastUpdateTime = currentTime;
|
||||
}
|
||||
}
|
||||
493
tests/AcDream.Core.Tests/Physics/PhysicsBodyTests.cs
Normal file
493
tests/AcDream.Core.Tests/Physics/PhysicsBodyTests.cs
Normal file
|
|
@ -0,0 +1,493 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for PhysicsBody — the C# port of CPhysicsObj's core simulation
|
||||
/// from acclient.exe (FUN_005111d0, FUN_00511420, FUN_00511ec0, FUN_00511fa0,
|
||||
/// FUN_00511de0, FUN_0050f940, FUN_00515020).
|
||||
/// </summary>
|
||||
public sealed class PhysicsBodyTests
|
||||
{
|
||||
// ── helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
private static PhysicsBody MakeAirborne()
|
||||
{
|
||||
var body = new PhysicsBody
|
||||
{
|
||||
State = PhysicsStateFlags.Gravity | PhysicsStateFlags.ReportCollisions,
|
||||
};
|
||||
// Airborne: not in Contact, not OnWalkable
|
||||
body.TransientState = TransientStateFlags.Active;
|
||||
return body;
|
||||
}
|
||||
|
||||
private static PhysicsBody MakeGrounded()
|
||||
{
|
||||
var body = new PhysicsBody
|
||||
{
|
||||
State = PhysicsStateFlags.Gravity | PhysicsStateFlags.ReportCollisions,
|
||||
};
|
||||
body.TransientState = TransientStateFlags.Contact | TransientStateFlags.OnWalkable | TransientStateFlags.Active;
|
||||
return body;
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// calc_acceleration
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void calc_acceleration_airborne_gravity_sets_minus_9_8_on_z()
|
||||
{
|
||||
var body = MakeAirborne();
|
||||
body.calc_acceleration();
|
||||
|
||||
Assert.Equal(0f, body.Acceleration.X);
|
||||
Assert.Equal(0f, body.Acceleration.Y);
|
||||
Assert.Equal(-9.8f, body.Acceleration.Z, precision: 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void calc_acceleration_grounded_zeros_acceleration_and_omega()
|
||||
{
|
||||
var body = MakeGrounded();
|
||||
body.Acceleration = new Vector3(1f, 2f, 3f);
|
||||
body.Omega = new Vector3(0.5f, 0.5f, 0.5f);
|
||||
body.calc_acceleration();
|
||||
|
||||
Assert.Equal(Vector3.Zero, body.Acceleration);
|
||||
Assert.Equal(Vector3.Zero, body.Omega);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void calc_acceleration_no_gravity_flag_zeros_acceleration()
|
||||
{
|
||||
var body = new PhysicsBody
|
||||
{
|
||||
State = PhysicsStateFlags.None, // no Gravity flag
|
||||
TransientState = TransientStateFlags.Active,
|
||||
};
|
||||
body.Acceleration = new Vector3(0f, 0f, -9.8f);
|
||||
body.calc_acceleration();
|
||||
|
||||
Assert.Equal(Vector3.Zero, body.Acceleration);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void calc_acceleration_sledding_airborne_still_applies_gravity()
|
||||
{
|
||||
// Sledding but not grounded — gravity still applies
|
||||
var body = new PhysicsBody
|
||||
{
|
||||
State = PhysicsStateFlags.Gravity | PhysicsStateFlags.Sledding,
|
||||
TransientState = TransientStateFlags.Active,
|
||||
};
|
||||
body.calc_acceleration();
|
||||
|
||||
Assert.Equal(-9.8f, body.Acceleration.Z, precision: 6);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// UpdatePhysicsInternal — Euler integration
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void UpdatePhysicsInternal_integrates_position_correctly_one_step()
|
||||
{
|
||||
// Analytical: x(t) = x0 + v0*t + 0.5*a*t²
|
||||
// With v0=(1,0,0), a=(0,0,-9.8), dt=0.1
|
||||
// x = 0.1
|
||||
// z = 0.5 * (-9.8) * 0.01 = -0.049
|
||||
var body = MakeAirborne();
|
||||
body.Velocity = new Vector3(1f, 0f, 0f);
|
||||
body.Acceleration = new Vector3(0f, 0f, -9.8f);
|
||||
|
||||
body.UpdatePhysicsInternal(0.1f);
|
||||
|
||||
Assert.Equal(0.1f, body.Position.X, precision: 5);
|
||||
Assert.Equal(0f, body.Position.Y, precision: 5);
|
||||
// 0.5 * (-9.8) * 0.01 = -0.049
|
||||
Assert.Equal(-0.049f, body.Position.Z, precision: 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdatePhysicsInternal_velocity_updated_by_acceleration_times_dt()
|
||||
{
|
||||
var body = MakeAirborne();
|
||||
body.Velocity = new Vector3(0f, 0f, 0f);
|
||||
body.Acceleration = new Vector3(0f, 0f, -9.8f);
|
||||
|
||||
body.UpdatePhysicsInternal(0.5f);
|
||||
|
||||
// velocity += accel * dt = (0, 0, -9.8 * 0.5) = (0, 0, -4.9)
|
||||
Assert.Equal(0f, body.Velocity.X, precision: 5);
|
||||
Assert.Equal(0f, body.Velocity.Y, precision: 5);
|
||||
Assert.Equal(-4.9f, body.Velocity.Z, precision: 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdatePhysicsInternal_multiple_frames_accumulates_correctly()
|
||||
{
|
||||
// Free-fall from rest under gravity for N frames of dt each.
|
||||
// Analytical z(t) = 0.5 * g * t² where g = -9.8
|
||||
// After 10 frames of 0.1 s each (total t=1.0 s):
|
||||
// z = 0.5 * (-9.8) * 1.0 = -4.9
|
||||
// The Euler integrator accumulates small truncation error, so allow 2% tolerance.
|
||||
var body = MakeAirborne();
|
||||
body.Velocity = Vector3.Zero;
|
||||
body.Acceleration = new Vector3(0f, 0f, PhysicsBody.Gravity);
|
||||
|
||||
const int frames = 10;
|
||||
const float dt = 0.1f;
|
||||
for (int i = 0; i < frames; i++)
|
||||
body.UpdatePhysicsInternal(dt);
|
||||
|
||||
float expected = 0.5f * PhysicsBody.Gravity * (frames * dt) * (frames * dt);
|
||||
Assert.True(MathF.Abs(body.Position.Z - expected) < 0.15f,
|
||||
$"Expected z ≈ {expected:F4}, got {body.Position.Z:F4}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdatePhysicsInternal_zero_velocity_clears_active_flag_when_grounded()
|
||||
{
|
||||
var body = MakeGrounded();
|
||||
body.Velocity = Vector3.Zero;
|
||||
body.TransientState |= TransientStateFlags.Active;
|
||||
|
||||
body.UpdatePhysicsInternal(0.1f);
|
||||
|
||||
Assert.False(body.IsActive);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// set_velocity — velocity clamping
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void set_velocity_below_max_stores_velocity_unchanged()
|
||||
{
|
||||
var body = new PhysicsBody();
|
||||
var v = new Vector3(10f, 5f, 2f);
|
||||
body.set_velocity(v);
|
||||
|
||||
Assert.Equal(v, body.Velocity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void set_velocity_above_max_clamps_to_MaxVelocity_magnitude()
|
||||
{
|
||||
var body = new PhysicsBody();
|
||||
// velocity with magnitude > 50
|
||||
var v = new Vector3(100f, 0f, 0f);
|
||||
body.set_velocity(v);
|
||||
|
||||
Assert.True(body.Velocity.Length() <= PhysicsBody.MaxVelocity + 1e-4f,
|
||||
$"Velocity magnitude {body.Velocity.Length()} exceeds MaxVelocity {PhysicsBody.MaxVelocity}");
|
||||
Assert.Equal(PhysicsBody.MaxVelocity, body.Velocity.Length(), precision: 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void set_velocity_diagonal_above_max_clamps_and_preserves_direction()
|
||||
{
|
||||
var body = new PhysicsBody();
|
||||
var dir = Vector3.Normalize(new Vector3(3f, 4f, 0f)); // unit vector
|
||||
var v = dir * 80f; // magnitude = 80 > 50
|
||||
body.set_velocity(v);
|
||||
|
||||
Assert.Equal(PhysicsBody.MaxVelocity, body.Velocity.Length(), precision: 3);
|
||||
// Direction should be preserved
|
||||
var resultDir = Vector3.Normalize(body.Velocity);
|
||||
Assert.Equal(dir.X, resultDir.X, precision: 4);
|
||||
Assert.Equal(dir.Y, resultDir.Y, precision: 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void set_velocity_sets_active_flag()
|
||||
{
|
||||
var body = new PhysicsBody();
|
||||
body.TransientState = TransientStateFlags.None;
|
||||
body.set_velocity(new Vector3(1f, 0f, 0f));
|
||||
|
||||
Assert.True(body.IsActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void set_velocity_exactly_at_max_is_not_clamped()
|
||||
{
|
||||
var body = new PhysicsBody();
|
||||
var v = new Vector3(PhysicsBody.MaxVelocity, 0f, 0f);
|
||||
body.set_velocity(v);
|
||||
|
||||
Assert.Equal(v.X, body.Velocity.X, precision: 4);
|
||||
Assert.Equal(0f, body.Velocity.Y, precision: 4);
|
||||
Assert.Equal(0f, body.Velocity.Z, precision: 4);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// set_local_velocity — body→world transform
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void set_local_velocity_identity_orientation_passes_through()
|
||||
{
|
||||
var body = new PhysicsBody { Orientation = Quaternion.Identity };
|
||||
body.set_local_velocity(new Vector3(1f, 0f, 0f));
|
||||
|
||||
Assert.Equal(1f, body.Velocity.X, precision: 5);
|
||||
Assert.Equal(0f, body.Velocity.Y, precision: 5);
|
||||
Assert.Equal(0f, body.Velocity.Z, precision: 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void set_local_velocity_90_degree_yaw_rotates_forward_to_right()
|
||||
{
|
||||
// A 90° CCW rotation around Z maps +X in local space to +Y in world space.
|
||||
var body = new PhysicsBody
|
||||
{
|
||||
Orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, MathF.PI / 2f)
|
||||
};
|
||||
body.set_local_velocity(new Vector3(1f, 0f, 0f));
|
||||
|
||||
// After 90° yaw: local +X becomes world +Y (approximately)
|
||||
Assert.True(MathF.Abs(body.Velocity.X) < 1e-4f, $"Expected Vx≈0, got {body.Velocity.X}");
|
||||
Assert.True(MathF.Abs(body.Velocity.Y - 1f) < 1e-4f, $"Expected Vy≈1, got {body.Velocity.Y}");
|
||||
Assert.True(MathF.Abs(body.Velocity.Z) < 1e-4f, $"Expected Vz≈0, got {body.Velocity.Z}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void set_local_velocity_180_degree_yaw_reverses_horizontal_forward()
|
||||
{
|
||||
var body = new PhysicsBody
|
||||
{
|
||||
Orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, MathF.PI)
|
||||
};
|
||||
body.set_local_velocity(new Vector3(1f, 0f, 0f));
|
||||
|
||||
Assert.True(MathF.Abs(body.Velocity.X + 1f) < 1e-4f, $"Expected Vx≈-1, got {body.Velocity.X}");
|
||||
Assert.True(MathF.Abs(body.Velocity.Y) < 1e-4f, $"Expected Vy≈0, got {body.Velocity.Y}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void set_local_velocity_magnitude_preserved_after_rotation()
|
||||
{
|
||||
var body = new PhysicsBody
|
||||
{
|
||||
Orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, 1.23f)
|
||||
};
|
||||
var localVel = new Vector3(3f, 4f, 0f);
|
||||
body.set_local_velocity(localVel);
|
||||
|
||||
Assert.Equal(localVel.Length(), body.Velocity.Length(), precision: 4);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// set_on_walkable
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void set_on_walkable_true_sets_OnWalkable_flag()
|
||||
{
|
||||
var body = MakeAirborne();
|
||||
body.set_on_walkable(true);
|
||||
|
||||
Assert.True(body.OnWalkable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void set_on_walkable_false_clears_OnWalkable_flag()
|
||||
{
|
||||
var body = MakeGrounded();
|
||||
body.set_on_walkable(false);
|
||||
|
||||
Assert.False(body.OnWalkable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void set_on_walkable_true_also_calls_calc_acceleration_zeroing_accel()
|
||||
{
|
||||
// When Contact + OnWalkable (non-sledding): acceleration should be zeroed.
|
||||
var body = new PhysicsBody
|
||||
{
|
||||
State = PhysicsStateFlags.Gravity | PhysicsStateFlags.ReportCollisions,
|
||||
TransientState = TransientStateFlags.Contact,
|
||||
Acceleration = new Vector3(0f, 0f, -9.8f),
|
||||
};
|
||||
body.set_on_walkable(true);
|
||||
|
||||
Assert.Equal(Vector3.Zero, body.Acceleration);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void set_on_walkable_false_allows_gravity_to_apply()
|
||||
{
|
||||
var body = MakeGrounded();
|
||||
body.set_on_walkable(false);
|
||||
|
||||
// After clearing OnWalkable, calc_acceleration should apply gravity.
|
||||
Assert.Equal(-9.8f, body.Acceleration.Z, precision: 6);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// calc_friction
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void calc_friction_not_on_walkable_does_nothing()
|
||||
{
|
||||
var body = MakeAirborne();
|
||||
body.Velocity = new Vector3(5f, 0f, 0f);
|
||||
var before = body.Velocity;
|
||||
body.calc_friction(0.1f, body.Velocity.LengthSquared());
|
||||
|
||||
Assert.Equal(before, body.Velocity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void calc_friction_velocity_parallel_to_ground_reduces_magnitude()
|
||||
{
|
||||
// Ground normal = +Z, velocity is horizontal (no inward component),
|
||||
// but if we tilt slightly downward (dot < 0) friction fires.
|
||||
var body = MakeGrounded();
|
||||
body.GroundNormal = Vector3.UnitZ;
|
||||
// Give a small downward Z component so dot(normal, vel) < 0
|
||||
body.Velocity = new Vector3(5f, 0f, -0.1f);
|
||||
float mag2 = body.Velocity.LengthSquared();
|
||||
|
||||
body.calc_friction(0.1f, mag2);
|
||||
|
||||
// Speed should be reduced by friction
|
||||
Assert.True(body.Velocity.Length() < new Vector3(5f, 0f, 0f).Length(),
|
||||
"Friction should reduce velocity magnitude");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void calc_friction_velocity_moving_away_from_normal_no_change()
|
||||
{
|
||||
// dot(GroundNormal=(0,0,1), velocity=(5,0,1)) = 1 > 0 → no friction
|
||||
var body = MakeGrounded();
|
||||
body.GroundNormal = Vector3.UnitZ;
|
||||
body.Velocity = new Vector3(5f, 0f, 1f); // moving up = away from ground
|
||||
var before = body.Velocity;
|
||||
float mag2 = body.Velocity.LengthSquared();
|
||||
|
||||
body.calc_friction(0.1f, mag2);
|
||||
|
||||
Assert.Equal(before, body.Velocity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void calc_friction_zero_friction_coefficient_no_reduction()
|
||||
{
|
||||
var body = MakeGrounded();
|
||||
body.GroundNormal = Vector3.UnitZ;
|
||||
body.Velocity = new Vector3(5f, 0f, -0.01f);
|
||||
body.Friction = 0f; // frictionless surface
|
||||
float mag2 = body.Velocity.LengthSquared();
|
||||
|
||||
body.calc_friction(0.1f, mag2);
|
||||
|
||||
// After removing normal component, velocity magnitude should be ≈ 5 (horizontal)
|
||||
// With friction=0, pow(1-0, dt)=1, so velocity unchanged beyond normal removal
|
||||
Assert.True(body.Velocity.Length() > 4.9f,
|
||||
$"Zero friction: speed {body.Velocity.Length()} should stay near 5");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void calc_friction_removes_normal_component_from_velocity()
|
||||
{
|
||||
// Velocity = (1, 0, -1), GroundNormal = (0, 0, 1)
|
||||
// dot = -1 → velocity -= (-1) * (0,0,1) = velocity + (0,0,1) → (1, 0, 0)
|
||||
var body = MakeGrounded();
|
||||
body.GroundNormal = Vector3.UnitZ;
|
||||
body.Friction = 0f; // no friction to isolate normal-removal behavior
|
||||
body.Velocity = new Vector3(1f, 0f, -1f);
|
||||
float mag2 = body.Velocity.LengthSquared();
|
||||
|
||||
body.calc_friction(1.0f, mag2);
|
||||
|
||||
// After normal removal the Z component should be zero (or very small).
|
||||
Assert.True(MathF.Abs(body.Velocity.Z) < 1e-4f,
|
||||
$"Normal component should be removed; Vz = {body.Velocity.Z}");
|
||||
Assert.Equal(1f, body.Velocity.X, precision: 4);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// update_object — per-frame driver
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void update_object_dt_below_min_quantum_does_not_advance()
|
||||
{
|
||||
var body = MakeAirborne();
|
||||
body.Velocity = new Vector3(1f, 0f, 0f);
|
||||
body.Acceleration = Vector3.Zero;
|
||||
body.LastUpdateTime = 0.0;
|
||||
|
||||
// Advance by less than MinQuantum — should be a no-op
|
||||
body.update_object(PhysicsBody.MinQuantum * 0.5);
|
||||
|
||||
Assert.Equal(Vector3.Zero, body.Position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void update_object_dt_above_huge_quantum_consumes_time_without_simulating()
|
||||
{
|
||||
var body = MakeAirborne();
|
||||
body.Velocity = new Vector3(1f, 0f, 0f);
|
||||
body.Acceleration = Vector3.Zero;
|
||||
body.LastUpdateTime = 0.0;
|
||||
|
||||
body.update_object(PhysicsBody.HugeQuantum + 0.5);
|
||||
|
||||
// Time consumed but no physics step — position unchanged
|
||||
Assert.Equal(Vector3.Zero, body.Position);
|
||||
Assert.Equal(PhysicsBody.HugeQuantum + 0.5, body.LastUpdateTime, precision: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void update_object_advances_position_over_valid_dt()
|
||||
{
|
||||
var body = MakeAirborne();
|
||||
// No friction or gravity interference — just pure horizontal velocity
|
||||
body.State = PhysicsStateFlags.None; // no gravity
|
||||
body.Velocity = new Vector3(10f, 0f, 0f);
|
||||
body.LastUpdateTime = 0.0;
|
||||
|
||||
double dt = 0.1;
|
||||
body.update_object(dt);
|
||||
|
||||
// x ≈ 10 * 0.1 = 1.0 (ignoring sub-step rounding)
|
||||
Assert.True(body.Position.X > 0f, "Position should have advanced");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void update_object_updates_LastUpdateTime()
|
||||
{
|
||||
var body = MakeAirborne();
|
||||
body.LastUpdateTime = 0.0;
|
||||
body.State = PhysicsStateFlags.None;
|
||||
|
||||
double t = 0.05;
|
||||
body.update_object(t);
|
||||
|
||||
Assert.Equal(t, body.LastUpdateTime, precision: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void update_object_gravity_free_fall_accumulates_downward_velocity()
|
||||
{
|
||||
var body = MakeAirborne();
|
||||
// Let it fall for one valid quantum
|
||||
body.LastUpdateTime = 0.0;
|
||||
double dt = PhysicsBody.MinQuantum * 2; // > MinQuantum but < HugeQuantum
|
||||
|
||||
body.update_object(dt);
|
||||
|
||||
// After one step velocity should be negative Z
|
||||
Assert.True(body.Velocity.Z < 0f,
|
||||
$"Gravity should produce negative Z velocity; got {body.Velocity.Z}");
|
||||
}
|
||||
}
|
||||
0
tools/ghidra_project/acclient.gpr
Normal file
0
tools/ghidra_project/acclient.gpr
Normal file
11
tools/ghidra_project/acclient.rep/idata/00/00000000.prp
Normal file
11
tools/ghidra_project/acclient.rep/idata/00/00000000.prp
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<FILE_INFO>
|
||||
<BASIC_INFO>
|
||||
<STATE NAME="CONTENT_TYPE" TYPE="string" VALUE="Program" />
|
||||
<STATE NAME="PARENT" TYPE="string" VALUE="/" />
|
||||
<STATE NAME="FILE_ID" TYPE="string" VALUE="c0a8162cd88181512443538400" />
|
||||
<STATE NAME="FILE_TYPE" TYPE="int" VALUE="0" />
|
||||
<STATE NAME="READ_ONLY" TYPE="boolean" VALUE="false" />
|
||||
<STATE NAME="NAME" TYPE="string" VALUE="acclient.exe" />
|
||||
</BASIC_INFO>
|
||||
</FILE_INFO>
|
||||
BIN
tools/ghidra_project/acclient.rep/idata/00/~00000000.db/db.3.gbf
Normal file
BIN
tools/ghidra_project/acclient.rep/idata/00/~00000000.db/db.3.gbf
Normal file
Binary file not shown.
BIN
tools/ghidra_project/acclient.rep/idata/00/~00000000.db/db.4.gbf
Normal file
BIN
tools/ghidra_project/acclient.rep/idata/00/~00000000.db/db.4.gbf
Normal file
Binary file not shown.
5
tools/ghidra_project/acclient.rep/idata/~index.bak
Normal file
5
tools/ghidra_project/acclient.rep/idata/~index.bak
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
VERSION=1
|
||||
/
|
||||
00000000:acclient.exe:c0a8162cd88181512443538400
|
||||
NEXT-ID:1
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
5
tools/ghidra_project/acclient.rep/idata/~index.dat
Normal file
5
tools/ghidra_project/acclient.rep/idata/~index.dat
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
VERSION=1
|
||||
/
|
||||
00000000:acclient.exe:c0a8162cd88181512443538400
|
||||
NEXT-ID:1
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
6
tools/ghidra_project/acclient.rep/project.prp
Normal file
6
tools/ghidra_project/acclient.rep/project.prp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<FILE_INFO>
|
||||
<BASIC_INFO>
|
||||
<STATE NAME="OWNER" TYPE="string" VALUE="erikn" />
|
||||
</BASIC_INFO>
|
||||
</FILE_INFO>
|
||||
4
tools/ghidra_project/acclient.rep/user/~index.dat
Normal file
4
tools/ghidra_project/acclient.rep/user/~index.dat
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
VERSION=1
|
||||
/
|
||||
NEXT-ID:0
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
4
tools/ghidra_project/acclient.rep/versioned/~index.bak
Normal file
4
tools/ghidra_project/acclient.rep/versioned/~index.bak
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
VERSION=1
|
||||
/
|
||||
NEXT-ID:0
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
4
tools/ghidra_project/acclient.rep/versioned/~index.dat
Normal file
4
tools/ghidra_project/acclient.rep/versioned/~index.dat
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
VERSION=1
|
||||
/
|
||||
NEXT-ID:0
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,5 @@
|
|||
VERSION=1
|
||||
/
|
||||
00000000:acclient.exe:c0a8162ccd0182120605721300
|
||||
NEXT-ID:1
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
0
tools/ghidra_project/acclient_full2/acclient_full2.gpr
Normal file
0
tools/ghidra_project/acclient_full2/acclient_full2.gpr
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<FILE_INFO>
|
||||
<BASIC_INFO>
|
||||
<STATE NAME="CONTENT_TYPE" TYPE="string" VALUE="Program" />
|
||||
<STATE NAME="PARENT" TYPE="string" VALUE="/" />
|
||||
<STATE NAME="FILE_ID" TYPE="string" VALUE="c0a8162d356182196155907400" />
|
||||
<STATE NAME="FILE_TYPE" TYPE="int" VALUE="0" />
|
||||
<STATE NAME="READ_ONLY" TYPE="boolean" VALUE="false" />
|
||||
<STATE NAME="NAME" TYPE="string" VALUE="acclient.exe" />
|
||||
</BASIC_INFO>
|
||||
</FILE_INFO>
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,4 @@
|
|||
VERSION=1
|
||||
/
|
||||
NEXT-ID:0
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
VERSION=1
|
||||
/
|
||||
00000000:acclient.exe:c0a8162d356182196155907400
|
||||
NEXT-ID:1
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
IADD:00000000:/acclient.exe
|
||||
IDSET:/acclient.exe:c0a8162d356182196155907400
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<FILE_INFO>
|
||||
<BASIC_INFO>
|
||||
<STATE NAME="OWNER" TYPE="string" VALUE="erikn" />
|
||||
</BASIC_INFO>
|
||||
</FILE_INFO>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
VERSION=1
|
||||
/
|
||||
NEXT-ID:0
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
VERSION=1
|
||||
/
|
||||
NEXT-ID:0
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
VERSION=1
|
||||
/
|
||||
NEXT-ID:0
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<FILE_INFO>
|
||||
<BASIC_INFO>
|
||||
<STATE NAME="CONTENT_TYPE" TYPE="string" VALUE="Program" />
|
||||
<STATE NAME="PARENT" TYPE="string" VALUE="/" />
|
||||
<STATE NAME="FILE_ID" TYPE="string" VALUE="c0a8162f0d1181714693391400" />
|
||||
<STATE NAME="FILE_TYPE" TYPE="int" VALUE="0" />
|
||||
<STATE NAME="READ_ONLY" TYPE="boolean" VALUE="false" />
|
||||
<STATE NAME="NAME" TYPE="string" VALUE="acclient.exe" />
|
||||
</BASIC_INFO>
|
||||
</FILE_INFO>
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,5 @@
|
|||
VERSION=1
|
||||
/
|
||||
00000000:acclient.exe:c0a8162f0d1181714693391400
|
||||
NEXT-ID:1
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
VERSION=1
|
||||
/
|
||||
00000000:acclient.exe:c0a8162f0d1181714693391400
|
||||
NEXT-ID:1
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<FILE_INFO>
|
||||
<BASIC_INFO>
|
||||
<STATE NAME="OWNER" TYPE="string" VALUE="erikn" />
|
||||
</BASIC_INFO>
|
||||
</FILE_INFO>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
VERSION=1
|
||||
/
|
||||
NEXT-ID:0
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
VERSION=1
|
||||
/
|
||||
NEXT-ID:0
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
VERSION=1
|
||||
/
|
||||
NEXT-ID:0
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
Loading…
Add table
Add a link
Reference in a new issue