// =============================================================================
// SCUM Auto-Voltage Solver — Forum Guide
// =============================================================================
//
// What this does
// --------------
// SCUM's "Voltage Matching" minigame asks you to flip a set of switches so
// that an input number, run through two parallel chains of math operations,
// produces two target output numbers (A and B) at the same time.
//
// Each switch maps to ONE pair of operations: { Left op, Right op }.
// - "Left op" contributes to Output A
// - "Right op" contributes to Output B
//
// Flip switch ON -> its pair is applied to both chains, in order
// Flip switch OFF -> its pair is skipped
//
// With at most 8 switches and 4 operation types (+, -, *, /), the entire
// search space is 2^8 = 256 combinations — trivial to brute-force. The hard
// part isn't the math; it's getting the data out of the UE4 object cleanly.
//
// This post covers:
// 1. The class layout we care about
// 2. How to safely read each property (FindOffset + fallback pattern)
// 3. The solver itself
// 4. An overlay that tells the player exactly which switches to flip
=============================================================================
#pragma once
#include <cmath>
#include <vector>
#include <map>
#include <string>
#include <imgui.h>
// -----------------------------------------------------------------------------
// 1) Class layout (from Dumper-7 SDK)
// -----------------------------------------------------------------------------
//
// // Class SCUM.VoltageMatchingMinigame
// // 0x01A8 (0x0568 - 0x03C0)
// class AVoltageMatchingMinigame final : public AMinigame
// {
// uint8 Pad_3C0[0x20]; // 0x03C0
// USwitchboardComponent* _switchboardComponent; // 0x03E0
// ...
// TArray<FVoltageMatchingSwitchElementsPairing> _switchPairings; // 0x0480
// TArray<UStaticMeshComponent*> _electricalElementsMeshComponents; // 0x0490
// TArray<FVoltageMatchingElementRow> _elementRows; // 0x04A0
// float _timeLimit; // 0x04B0
// float _waitingForPlayerTimeLimit; // 0x04B4
// ...
// TArray<uint32> _additionValues; // 0x04D0
// TArray<uint32> _subtractionValues; // 0x04E0
// TArray<uint32> _multiplicationValues; // 0x04F0
// TArray<uint32> _divisionValues; // 0x0500
// TArray<uint32> _inputValues; // 0x0510
// int32 _maximumNumberOfElementsPerType; // 0x0520
// int32 _minActiveSwitches; // 0x0524
// int32 _maxActiveSwitches; // 0x0528
// uint8 Pad_52C[0x3C]; // 0x052C <-- hidden state lives here
// };
//
// Three things live inside that Pad_52C[0x3C] block that aren't named in the
// dump but matter for solving. We found them by walking memory:
//
// 0x0530 TArray<FOperationDefinition> // (Data ptr at +0x530, Count at +0x538)
// 0x0548 float CurrentInputValue // the actual input shown on screen
// 0x054C float ElapsedSolveTime
// 0x0550 float ElapsedWaitingTime
//
// FOperationDefinition entries are 0x10 bytes wide:
//
// +0x00 uint64 Identifier // matches IDs stored in switch pairings
// +0x08 uint8 OperationType // 0=NOP, 1=+, 2=-, 3=*, 4=/
// +0x0C float Value // operand for the operation
//
// FVoltageMatchingSwitchElementsPairing entries are 0x18 bytes wide:
//
// +0x00 ??? // owner / state byte we don't need
// +0x08 uint64* OperationIds.Data
// +0x10 int32 OperationIds.Count // 2 (Left at [0], Right at [1])
//
// FVoltageMatchingElementRow entries are 0x18 bytes wide (we treat them as
// two rows of 0x18 = one struct holding both outputs):
//
// row[0] (output A): +0x10 = current, +0x14 = target
// row[1] (output B): +0x28 = current, +0x2C = target (i.e. row[1] +0x10/+0x14)
//
// -----------------------------------------------------------------------------
namespace VoltageSolver
{
// -------------------------------------------------------------------------
// 2) Safe property reading — FindOffset + fallback
// -------------------------------------------------------------------------
//
// We never trust a hardcoded offset blindly. The primary source is the
// engine's property table: given the class name and property name, the
// engine walks the UClass and returns the field offset. If that fails
// (engine reflection stripped, class renamed, etc.) we fall back to the
// hardcoded value from the dump.
//
// This pattern is what keeps the solver alive across small game updates —
// properties keep their names even when struct sizes shift.
//
// The functions below are skeletons. Replace `UnrealEngine::FindOffset`
// with whatever your project uses to walk UProperty linked lists.
// -------------------------------------------------------------------------
template <typename T>
static T ReadProperty(void* self, const char* propName, uint32_t fallback)
{
static uint32_t cached = 0;
if (!cached) {
cached = UnrealEngine::FindOffset(L"VoltageMatchingMinigame", propName);
if (!cached) cached = fallback;
}
if (!self) return {};
__try { return *reinterpret_cast<T*>((uintptr_t)self + cached); }
__except (1) { return {}; }
}
// For TArrays we just expose Data/Count/Max — the actual element layout is
// walked by hand in the solver because UE4 array templates don't survive
// generic reinterpret_cast cleanly across module boundaries.
struct RawArray { uintptr_t Data; int32_t Count; int32_t Max; };
static RawArray ReadArray(void* self, const char* propName, uint32_t fallback)
{
static uint32_t cached = 0;
if (!cached) {
cached = UnrealEngine::FindOffset(L"VoltageMatchingMinigame", propName);
if (!cached) cached = fallback;
}
if (!self) return {};
__try {
struct R { void* d; int32_t c; int32_t m; };
auto* a = reinterpret_cast<R*>((uintptr_t)self + cached);
return { (uintptr_t)a->d, a->c, a->m };
}
__except (1) { return {}; }
}
// -------------------------------------------------------------------------
// 3) Snapshot — everything we need to solve one frame of the puzzle
// -------------------------------------------------------------------------
struct Operation { uint64_t Id; uint8_t Type; float Value; bool Valid; };
struct SwitchRule {
int Index; // 1..8 (display number)
uint64_t LeftId;
uint64_t RightId;
Operation Left;
Operation Right;
bool IsActive; // currently flipped ON in-game
bool ShouldBeActive; // solver's recommendation
};
struct Snapshot {
float InputValue;
float OutputACurrent, OutputATarget;
float OutputBCurrent, OutputBTarget;
float ElapsedSolveTime, ElapsedWaitingTime;
std::vector<uint16_t> ActiveSwitchIds; // from USwitchboardComponent
};
// -------------------------------------------------------------------------
// 4) Reading the puzzle data
// -------------------------------------------------------------------------
//
// ElementRows is the trickiest piece. It's a TArray of structs, but the
// first ~0x10 bytes of each row are meta we don't care about. The two
// floats we want (current, target) live at +0x10 and +0x14 of each row.
// Row 0 = Output A, Row 1 = Output B.
//
// Rather than write a row struct, we just deref the TArray.Data pointer
// once and add the magic offsets. The "magic" pair (0x10/0x14 and
// 0x28/0x2C) is just row[0]+0x10/0x14 and row[1]+0x10/0x14 where each
// row is 0x18 bytes — i.e. base + (rowIndex * 0x18) + 0x10.
// -------------------------------------------------------------------------
static float ReadElementRowFloat(void* mg, uint32_t fieldOffset)
{
if (!mg) return 0.0f;
__try {
// _elementRows.Data lives at offset 0x4A0 of AVoltageMatchingMinigame
auto* dataPtr = (uintptr_t*)((uintptr_t)mg + 0x4A0);
uintptr_t base = *dataPtr;
if (!base) return 0.0f;
return *(float*)(base + fieldOffset);
}
__except (1) { return 0.0f; }
}
static Operation ReadOperation(void* mg, int index)
{
if (!mg || index < 0) return {};
__try {
auto* defsData = (uintptr_t*)((uintptr_t)mg + 0x530); // TArray.Data
auto* defsCount = (int32_t*) ((uintptr_t)mg + 0x538); // TArray.Count
if (!*defsData || index >= *defsCount) return {};
uintptr_t entry = *defsData + (uintptr_t)index * 0x10;
uint64_t id = *(uint64_t*)(entry + 0x0);
uint8_t op = *(uint8_t*) (entry + 0x8);
float val = *(float*) (entry + 0xC);
if (op > 4) return {}; // unknown op, ignore
return { id, op, val, true };
}
__except (1) { return {}; }
}
static bool ReadSwitchPair(void* mg, int switchIndex, uint64_t& outLeft, uint64_t& outRight)
{
if (!mg || switchIndex < 0) return false;
__try {
auto arr = ReadArray(mg, "_switchPairings", 0x0480);
if (!arr.Data || switchIndex >= arr.Count) return false;
uintptr_t entry = arr.Data + (uintptr_t)switchIndex * 0x18;
auto* opIdsData = *(uint64_t**)(entry + 0x8);
auto opIdsCount = *(int32_t*) (entry + 0x10);
if (!opIdsData || opIdsCount <= 0) return false;
outLeft = opIdsData[0];
outRight = opIdsCount > 1 ? opIdsData[1] : 0;
return true;
}
__except (1) { return false; }
}
// -------------------------------------------------------------------------
// 5) Building the snapshot
// -------------------------------------------------------------------------
static constexpr int kMaxSwitches = 8;
static constexpr int kMaxOpDefs = 32;
static bool BuildSnapshot(void* mg, Snapshot& snap, std::vector<SwitchRule>& rules)
{
if (!mg) return false;
// Hidden floats inside Pad_52C
snap.InputValue = *(float*)((uintptr_t)mg + 0x548);
snap.ElapsedSolveTime = *(float*)((uintptr_t)mg + 0x54C);
snap.ElapsedWaitingTime = *(float*)((uintptr_t)mg + 0x550);
// ElementRows[0] = output A, [1] = output B (each row 0x18 wide)
snap.OutputACurrent = ReadElementRowFloat(mg, 0x10);
snap.OutputATarget = ReadElementRowFloat(mg, 0x14);
snap.OutputBCurrent = ReadElementRowFloat(mg, 0x28);
snap.OutputBTarget = ReadElementRowFloat(mg, 0x2C);
// Build a lookup of operation ID -> definition so each switch can
// resolve its Left/Right operations in O(1).
std::map<uint64_t, Operation> ops;
for (int i = 0; i < kMaxOpDefs; ++i) {
auto op = ReadOperation(mg, i);
if (op.Valid) ops[op.Id] = op;
}
// Each switch references two op IDs
rules.clear();
rules.reserve(kMaxSwitches);
for (int s = 0; s < kMaxSwitches; ++s) {
uint64_t leftId = 0, rightId = 0;
if (!ReadSwitchPair(mg, s, leftId, rightId)) continue;
SwitchRule r{};
r.Index = s + 1;
r.LeftId = leftId;
r.RightId = rightId;
auto itL = ops.find(leftId); if (itL != ops.end()) r.Left = itL->second;
auto itR = ops.find(rightId); if (itR != ops.end()) r.Right = itR->second;
rules.push_back(r);
}
// (Optional) read live switch states from USwitchboardComponent here
// so the solver knows which switches are currently flipped ON.
return !rules.empty();
}
// -------------------------------------------------------------------------
// 6) The solver — brute force over 2^N switch masks
// -------------------------------------------------------------------------
//
// For each candidate mask (bit i = switch i is ON):
// - run the input through Left ops to compute Output A
// - run the input through Right ops to compute Output B
// - if both match the targets, it's a valid solution
//
// Among all valid solutions, prefer the one closest (Hamming distance)
// to the player's current switch configuration. This minimizes the
// number of switches the player has to physically flip, which matters
// because each flip has audio + animation latency in-game.
// -------------------------------------------------------------------------
static float ApplyOp(float v, const Operation& op)
{
switch (op.Type) {
case 1: return v + op.Value;
case 2: return v - op.Value;
case 3: return v * op.Value;
case 4: return op.Value != 0.0f ? v / op.Value : v;
default: return v; // NOP / unknown
}
}
static int PopCount(uint32_t v) { int c = 0; while (v) { c += v & 1; v >>= 1; } return c; }
static bool Approx(float a, float b) { return fabsf(a - b) <= 0.5f; }
static bool Solve(const Snapshot& snap, const std::vector<SwitchRule>& rules,
int minOn, int maxOn, uint32_t preferredMask, uint32_t& outMask)
{
const size_t N = rules.size();
if (!N) return false;
// Sanity: every switch must have both ops resolved
for (auto& r : rules)
if (!r.Left.Valid || !r.Right.Valid) return false;
bool found = false;
int bestDist = INT_MAX;
for (uint32_t mask = 0; mask < (1u << N); ++mask) {
int bits = PopCount(mask);
if (bits < minOn || bits > maxOn) continue;
float a = snap.InputValue;
float b = snap.InputValue;
for (size_t i = 0; i < N; ++i) {
if (!(mask & (1u << i))) continue;
a = ApplyOp(a, rules[i].Left);
b = ApplyOp(b, rules[i].Right);
}
if (!Approx(a, snap.OutputATarget) || !Approx(b, snap.OutputBTarget))
continue;
// Prefer solutions that require the fewest flips from current state
int dist = PopCount(mask ^ preferredMask);
if (!found || dist < bestDist) {
found = true;
bestDist = dist;
outMask = mask;
}
}
return found;
}
// -------------------------------------------------------------------------
// 7) ESP overlay — markers on switches + step list
// -------------------------------------------------------------------------
//
// The 8 left-column switches sit at fixed normalized screen positions on
// the voltage panel mesh. We hardcoded these from observation; if the
// panel ever changes, walk the USwitchboardComponent's mesh transforms
// and project them through the player camera instead.
// -------------------------------------------------------------------------
struct GuidePoint { float X, Y; };
static constexpr GuidePoint kSwitchGuidePoints[kMaxSwitches] = {
{ 0.3735f, 0.2204f }, { 0.3714f, 0.2935f },
{ 0.3710f, 0.3543f }, { 0.3732f, 0.4309f },
{ 0.3734f, 0.5002f }, { 0.3738f, 0.5730f },
{ 0.3701f, 0.6459f }, { 0.3725f, 0.7169f },
};
static void DrawGuide(const std::vector<SwitchRule>& rules, uint32_t solvedMask, bool hasSolution)
{
auto& io = ImGui::GetIO();
const float sw = io.DisplaySize.x, sh = io.DisplaySize.y;
auto* dl = ImGui::GetForegroundDrawList();
// Step list (top-left of screen)
ImVec2 cursor(20.0f, 20.0f);
dl->AddText(cursor, IM_COL32(120, 195, 255, 255), "[ AUTO VOLTAGE ]");
cursor.y += 22.0f;
if (!hasSolution) {
dl->AddText(cursor, IM_COL32(255, 110, 110, 255), "Waiting for solver data...");
return;
}
// Highlight each switch that needs to change
for (size_t i = 0; i < rules.size(); ++i) {
bool shouldBe = (solvedMask & (1u << i)) != 0;
if (shouldBe == rules[i].IsActive) continue;
ImU32 color = shouldBe ? IM_COL32(255, 220, 90, 255) // turn ON
: IM_COL32(255, 110, 110, 255); // turn OFF
// Marker on the physical switch
ImVec2 pos(kSwitchGuidePoints[i].X * sw, kSwitchGuidePoints[i].Y * sh);
dl->AddCircle(pos, 16.0f, color, 32, 2.5f);
dl->AddCircleFilled(pos, 4.5f, color);
// Step text in the side panel
char buf[32];
snprintf(buf, sizeof(buf), "S%zu TURN %s", i + 1, shouldBe ? "ON" : "OFF");
dl->AddText(cursor, color, buf);
cursor.y += 18.0f;
}
}
// -------------------------------------------------------------------------
// 8) Tick — call this from your main loop, with the minigame pointer
// -------------------------------------------------------------------------
static void Tick(void* minigame, int minOn = 2, int maxOn = 7)
{
if (!minigame) return;
Snapshot snap{};
std::vector<SwitchRule> rules;
if (!BuildSnapshot(minigame, snap, rules)) return;
// Build "currently active" mask from the live switch states you read
// out of USwitchboardComponent (omitted here for brevity).
uint32_t currentMask = 0;
for (size_t i = 0; i < rules.size(); ++i)
if (rules[i].IsActive) currentMask |= (1u << i);
uint32_t solvedMask = 0;
bool ok = Solve(snap, rules, minOn, maxOn, currentMask, solvedMask);
DrawGuide(rules, solvedMask, ok);
}
} // namespace VoltageSolver
=============================================================================
=============================================================================
Nice! Do you by chance have something for lockpick damage? I tried some things out but wasn't able to get it to work.
Tried so far:
- NOP patch on the durability function, server-side bypasses it on listen server
- Zeroing ALockpickingMinigame breaking multipliers (0x460/0x464), no Easy or tension tool multiplier field exists
- Freezing AItem._repPackedState (0x720), crashed, server drives it from an internal non-UPROPERTY variable anyway
- AItem._damageResponseFactor (0x574) to 0.0f, works maybe for the lockpick via _itemInHands, but going through LeftHandAttachmentSocket for the screwdriver crashes because the lock object sits in the same slot
Thinking vtable hook on AItem::TakeDamage or hooking OnLockpickDestroyed/OnTensionToolDestroyed on ALockpickingMinigame might be the way. Any ideas?
Nice! Do you by chance have something for lockpick damage? I tried some things out but wasn't able to get it to work.
Tried so far:
- NOP patch on the durability function, server-side bypasses it on listen server
- Zeroing ALockpickingMinigame breaking multipliers (0x460/0x464), no Easy or tension tool multiplier field exists
- Freezing AItem._repPackedState (0x720), crashed, server drives it from an internal non-UPROPERTY variable anyway
- AItem._damageResponseFactor (0x574) to 0.0f, works maybe for the lockpick via _itemInHands, but going through LeftHandAttachmentSocket for the screwdriver crashes because the lock object sits in the same slot
Thinking vtable hook on AItem::TakeDamage or hooking OnLockpickDestroyed/OnTensionToolDestroyed on ALockpickingMinigame might be the way. Any ideas?
Din't checked it to be honest, but dm me on discord we can talk more about this
[Suggestion] Dedicated Technical Discussion Threads "Reversal, Structs and Offsets" 05/27/2026 - Suggestions & Feedback - 6 Replies What if we had pinned topics in game categories to share structs, offsets, and things related to reverse engineering?
They could be managed by moderators or active members—it doesn't matter much, as long as they remain organized.
It would be interesting to have a single place where users can discuss reverse engineering techniques, offset tips, functions, and methods. This way, forum members can help each other and keep track of changes after updates. Currently, this type of information...
[Buying] Hiring programmer with considerable software reversal and anti-detection experience 01/28/2019 - Coders Trading - 0 Replies Hi
My last project was a success, all slots sold perfectly as agreed with my previous developer and we both left with a good sum of money (especially the developer!)
I am looking to venture into something new, so I'd like to discuss terms with a considerably experienced software programmer that specifically retains the following skill set:-
Anti-detection methods (RunPE, Process Manipulation/Injection/Read Memory/Write Memory, Ring0, Kernel Mode, Driver Signing, etc.)
Software...
[Coding] League of Legends Structs & Offsets 01/25/2019 - League of Legends Hacks, Bots, Cheats & Exploits - 2 Replies Updated to version: Version 9.1.260.635
Code:
#pragma once
#define TARGET_GAMEVERSION "Version 9.1.260.635 "
//Functions
#define oPrintChat 0x5B2710
#define oGetAttackDelay 0x589BA0 //0x12B9BA0
#define oGetAttackCastDelay 0x589AC0 //0x12B9AC0
[Sammelthread?] Fiesta Addys/Offsets/Structs 01/04/2016 - Fiesta Online - 29 Replies Da die ein oder anderen Personen sich kleine Tools zsm. schreiben und
eventuell Probleme haben die jeweiligen Addys/Offsets rauszusuchen,
dachte ich mir, ich erstelle dafür ein SammelThread.
Wer also die ein oder anderen Pointer findet, kann diese gerne im
Thread reinhauen, oder per PN an mich senden, damit ich diese hier
Updaten kann.
NeueVersion