Register for your free account! | Forgot your password?

Go Back   elitepvpers Shooter Scum
You last visited: Today at 06:51

  • Please register to post and access all features, it's quick, easy and FREE!

Advertisement



Reversal, Structs and Offsets

Discussion on Reversal, Structs and Offsets within the Scum forum part of the Shooter category.

Reply
 
Old   #1


 
teslatx's Avatar
 
elite*gold: 1598
The Black Market: 276/0/0
Join Date: May 2020
Posts: 2,555
Received Thanks: 425
Reversal, Structs and Offsets

Code:
// =============================================================================
// 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

=============================================================================
=============================================================================
teslatx is offline  
Thanks
1 User
Old   #2
 
RazexSkillz's Avatar
 
elite*gold: 36
Join Date: Jul 2013
Posts: 396
Received Thanks: 175
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?
RazexSkillz is offline  
Old   #3


 
teslatx's Avatar
 
elite*gold: 1598
The Black Market: 276/0/0
Join Date: May 2020
Posts: 2,555
Received Thanks: 425
Quote:
Originally Posted by RazexSkillz View Post
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
teslatx is offline  
Reply

Tags
scum


Similar Threads Similar Threads
[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



All times are GMT +2. The time now is 06:52.


Powered by vBulletin®
Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
SEO by vBSEO ©2011, Crawlability, Inc.

Support | Contact Us | FAQ | Advertising | Privacy Policy | Terms of Service | Abuse
Copyright ©2026 elitepvpers All Rights Reserved.