egametang/ET · error · ArgumentException

address offset of target and replace must less than ((1 << 2

Error message

address offset of target and replace must less than ((1 << 25) - 1)

What it means

Thrown by CodePatcher_arm32_near's constructor. On ARM32, a near branch (B instruction) encodes a 24-bit signed offset multiplied by 4, giving a ±32 MiB reach (2^25 - 1 bytes). If the absolute difference between the target function address and the replacement function address reaches or exceeds this limit, the single-instruction branch cannot reach and the constructor rejects the patch with ArgumentException.

Source

Thrown at Packages/cn.etetet.hybridclr/Scripts/Editor/Share/3rds/UnityHook/CodePatcher.cs:191

                ptr += 8;
                *ptr++ = 0x50;
                *ptr++ = 0xC3;
            }
            return ret;
        }
    }

    public unsafe class CodePatcher_arm32_near : CodePatcher
    {
        private static readonly byte[] s_jmpCode = new byte[]    // 4 bytes
        {
            0x00, 0x00, 0x00, 0xEA,                         // B $val   ; $val = (($dst - $src) / 4 - 2) & 0x1FFFFFF
        };

        public CodePatcher_arm32_near(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length)
        {
            if (Math.Abs((long)target - (long)replace) >= ((1 << 25) - 1))
                throw new ArgumentException("address offset of target and replace must less than ((1 << 25) - 1)");

#if ENABLE_HOOK_DEBUG
            Debug.Log($"CodePatcher_arm32_near: {PrintAddrs()}");
#endif
        }

        protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo)
        {
            byte[] ret = new byte[s_jmpCode.Length];
            int val = ((int)jmpTo - (int)jmpFrom) / 4 - 2;

            fixed (void* p = &ret[0])
            {
                byte* ptr = (byte*)p;
                *ptr++ = (byte)val;
                *ptr++ = (byte)(val >> 8);
                *ptr++ = (byte)(val >> 16);
                *ptr++ = 0xEA;

View on GitHub (pinned to 5cab01f7a8)

Solutions

  1. Use the far branch variant CodePatcher_arm32_far instead, which uses an 8-byte LDR PC sequence with no distance limit.
  2. If you control patcher selection, compute the offset first and choose near vs. far dynamically.
  3. On 32-bit ARM, consider whether the hook is necessary at all — prefer higher-level interception if address layout is uncontrollable.
  4. Check if the target runtime is actually 32-bit ARM; if it is 64-bit, use the arm64 patchers.

Example fix

// before
var patcher = new CodePatcher_arm32_near(targetPtr, replacePtr, proxyPtr);

// after — choose patcher based on distance
long offset = Math.Abs((long)targetPtr - (long)replacePtr);
CodePatcher patcher = offset < ((1 << 25) - 1)
    ? new CodePatcher_arm32_near(targetPtr, replacePtr, proxyPtr)
    : new CodePatcher_arm32_far(targetPtr, replacePtr, proxyPtr);
Defensive patterns

Strategy: validation

Validate before calling

long offset = Math.Abs((long)targetPtr - (long)replacePtr);
const long ARM32_NEAR_LIMIT = (1 << 25) - 1; // ~32 MiB
if (offset >= ARM32_NEAR_LIMIT)
{
    // Use far patcher instead, or abort with a clear message
    Debug.LogWarning($"ARM32 near branch cannot reach offset {offset}. Use CodePatcher_arm32_far.");
}

Type guard

static bool CanUseArm32Near(IntPtr target, IntPtr replace)
{
    return Math.Abs((long)target - (long)replace) < ((1 << 25) - 1);
}

Try / catch

try
{
    var patcher = new CodePatcher_arm32_near(targetPtr, replacePtr, proxyPtr);
}
catch (ArgumentException ex) when (ex.Message.Contains("must less than ((1 << 25)"))
{
    // Fall back to far patcher
    var patcher = new CodePatcher_arm32_far(targetPtr, replacePtr, proxyPtr);
}

Prevention

When it happens

Trigger: Constructing a CodePatcher_arm32_near where Math.Abs((long)target - (long)replace) >= 33,554,431 bytes. This occurs when ASLR or memory layout places the target method and its replacement in memory regions more than ~32 MiB apart — common when the replacement is in a separately loaded assembly or a JIT-allocated code region far from the original.

Common situations: Hooking a method on ARM32 (older Android devices, 32-bit iOS) where the hot-reload replacement function is loaded far from the original; using the near patcher unconditionally instead of letting the framework choose near vs. far based on actual distance; address space fragmentation on 32-bit systems.

Related errors


AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13). Data as JSON: /api/errors/4147fb2dda6ab85d. Report an issue: GitHub.