<?xml version='1.0' encoding='utf-8'?>
<CheatTable CheatEngineTableVersion="52">
  <CheatEntries>
    <CheatEntry>
      <ID>9324</ID>
      <Description>"ℹ Halo Weapon Lab - Table Information"</Description>
      <Options moHideChildren="1" moDeactivateChildrenAsWell="1" />
      <Color>F993BD</Color>
      <GroupHeader>1</GroupHeader>
      <CheatEntries>
        <CheatEntry>
          <ID>9337</ID>
          <Description>"Weapon Lab public edition: 1.0.2"</Description>
          <GroupHeader>1</GroupHeader>
        </CheatEntry>
        <CheatEntry>
          <ID>9325</ID>
          <Description>"Game: Halo Campaign Evolved (5.5.4.0)"</Description>
          <GroupHeader>1</GroupHeader>
        </CheatEntry>
        <CheatEntry>
          <ID>9326</ID>
          <Description>"Target process: HaloCampaignEvolved.exe"</Description>
          <GroupHeader>1</GroupHeader>
        </CheatEntry>
        <CheatEntry>
          <ID>9329</ID>
          <Description>"Known build: Meteorite+Rel-i343-Meteorite-2607-CU3 (5.5.4-1112544)"</Description>
          <GroupHeader>1</GroupHeader>
        </CheatEntry>
        <CheatEntry>
          <ID>9333</ID>
          <Description>"Public release / OCT ID: huqi"</Description>
          <GroupHeader>1</GroupHeader>
        </CheatEntry>
        <CheatEntry>
          <ID>9330</ID>
          <Description>"Credits: khuong - original table; selected low-level hook logic/code sites adapted with attribution"</Description>
          <GroupHeader>1</GroupHeader>
        </CheatEntry>
        <CheatEntry>
          <ID>9331</ID>
          <Description>"This public edition intentionally removes conventional trainer cheats and keeps Weapon Lab systems only"</Description>
          <GroupHeader>1</GroupHeader>
        </CheatEntry>
        <CheatEntry>
          <ID>9332</ID>
          <Description>"Single-player only"</Description>
          <GroupHeader>1</GroupHeader>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
    <CheatEntry>
      <ID>99200</ID>
      <Description>"Halo Weapon Lab"</Description>
      <Options moHideChildren="1" />
      <Color>00A5FF</Color>
      <GroupHeader>1</GroupHeader>
      <CheatEntries>
        <CheatEntry>
          <ID>80116</ID>
          <Description>"Instant Plasma Pistol MAX Charge"</Description>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]

aobscanmodule(InstantPlasmaChargeAOB,HaloSimulation_tag_release.dll,48 03 C5 40 88 B4 82 9C 02 00 00 48 8D 04 6D 00 00 00 00 48 03 C5)
assert(InstantPlasmaChargeAOB+3,40 88 B4 82 9C 02 00 00)

alloc(newmem,$1000,InstantPlasmaChargeAOB+3)
label(original)
label(return)

registersymbol(InstantPlasmaChargeAOB)

newmem:
  pushfq

  cmp rbp,0
  jne original

  cmp sil,01
  jne original

  popfq
  mov byte ptr [rdx+rax*4+0000029C],02
  jmp return

original:
  popfq
  mov [rdx+rax*4+0000029C],sil
  jmp return

InstantPlasmaChargeAOB+3:
  jmp newmem
  nop 3

return:

[DISABLE]

InstantPlasmaChargeAOB+3:
  db 40 88 B4 82 9C 02 00 00

unregistersymbol(InstantPlasmaChargeAOB)
dealloc(newmem)</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>99500</ID>
          <Description>"Energy Weapon Runtime Monitor (no infinite-energy modification)"</Description>
          <Options moHideChildren="1" moDeactivateChildrenAsWell="1" />
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
aobscanmodule(EnergyRuntimeAOB,HaloSimulation_tag_release.dll,C5 FA 58 C6 C5 F8 2F 05 ?? ?? ?? ?? C5 FA 11 87 D4 01 00 00)
alloc(EnergyRuntimeOriginal,12,EnergyRuntimeAOB)
registersymbol(EnergyRuntimeOriginal)
EnergyRuntimeOriginal:
  readmem(EnergyRuntimeAOB,12)
alloc(EnergyRuntimeNewmem,$1000,EnergyRuntimeAOB)
alloc(EnergyRuntimeData,8,EnergyRuntimeAOB)
label(EnergyRuntimeReturn)
label(currentEnergyWeaponBase)
registersymbol(EnergyRuntimeAOB)
registersymbol(currentEnergyWeaponBase)

EnergyRuntimeData:
currentEnergyWeaponBase:
  dq 0

EnergyRuntimeNewmem:
  // Capture the currently processed energy-weapon instance without changing energy behavior.
  mov [currentEnergyWeaponBase],rdi
  reassemble(EnergyRuntimeAOB)
  reassemble(EnergyRuntimeAOB+4)
  jmp EnergyRuntimeReturn

EnergyRuntimeAOB:
  jmp EnergyRuntimeNewmem
  nop 7
EnergyRuntimeReturn:

[DISABLE]
EnergyRuntimeAOB:
  readmem(EnergyRuntimeOriginal,12)
unregistersymbol(currentEnergyWeaponBase)
unregistersymbol(EnergyRuntimeAOB)
unregistersymbol(EnergyRuntimeOriginal)
dealloc(EnergyRuntimeData)
dealloc(EnergyRuntimeNewmem)
dealloc(EnergyRuntimeOriginal)</AssemblerScript>
          <CheatEntries>
            <CheatEntry>
              <ID>99501</ID>
              <Description>"Current Weapon Energy Usage (0 = full, 1 = empty)"</Description>
              <ShowAsSigned>0</ShowAsSigned>
              <VariableType>Float</VariableType>
              <Address>currentEnergyWeaponBase</Address>
              <Offsets>
                <Offset>1D4</Offset>
              </Offsets>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>80158</ID>
          <Description>"Fire Rate &amp; Volley Core (enable first)"</Description>
          <Options moHideChildren="1" />
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]

// ============================================================
// vmovss xmm7,[HaloSimulation_tag_release.dll+...]
// vmovaps xmm6,xmm0
// ============================================================

aobscanmodule(FriendlyWeaponCoreAOB,HaloSimulation_tag_release.dll,E8 ?? ?? ?? ?? 4C 8B 44 24 28 C5 FA 10 3D ?? ?? ?? ?? C5 F8 28 F0)

assert(FriendlyWeaponCoreAOB+0A,C5 FA 10 3D 84 8F 32 00 C5 F8 28 F0)


// ============================================================
//
//
//
// vmovss xmm1,[r12+r14+00000254]
// ============================================================

aobscanmodule(StableVolleyAOB,HaloSimulation_tag_release.dll,C4 81 7A 10 8C 34 54 02 00 00 C5 7A 10 05 ?? ?? ?? ??)

assert(StableVolleyAOB,C4 81 7A 10 8C 34 54 02 00 00)


// ============================================================
// ============================================================

alloc(FriendlyWeaponCoreNewmem,$1000,FriendlyWeaponCoreAOB+0A)
alloc(WeaponVolleyNewmem,$1000,StableVolleyAOB)
alloc(FriendlyWeaponCoreData,$1000,FriendlyWeaponCoreAOB+0A)

alloc(WeaponTypeCache,$4000,FriendlyWeaponCoreAOB+0A)


// ============================================================
// ============================================================

label(FriendlyWeaponCoreReturn)
label(FriendlyRateCheckPhysical)
label(FriendlyRateCheckEnergy)
label(FriendlyRateApplyRestore)
label(FriendlyRateSkipRestore)


// ============================================================
// ============================================================

label(WeaponVolleyReturn)
label(WeaponVolleyCacheEnergy)
label(WeaponVolleyCacheDone)

label(WeaponVolleyModeAllowed)
label(WeaponVolleyPhysicalOnly)
label(WeaponVolleyEnergyOnly)

label(WeaponVolleyMinimumOK)
label(WeaponVolleyMaximumOK)
label(WeaponVolleyNoAutoFill)
label(WeaponVolleyNoWrite)
label(WeaponVolleyFinish)


// ============================================================
// ============================================================

label(playerControlledWeaponBase)
label(fireIntervalScale)
label(oneFloat)

label(weaponEnhanceMode)

label(playerVolleyEnabled)
label(playerVolleyCount)
label(playerVolleyAutoFillMagazine)

label(playerPathHits)


// ============================================================
// ============================================================

registersymbol(FriendlyWeaponCoreAOB)
registersymbol(StableVolleyAOB)

registersymbol(playerControlledWeaponBase)
registersymbol(fireIntervalScale)
registersymbol(weaponEnhanceMode)

registersymbol(playerVolleyEnabled)
registersymbol(playerVolleyCount)
registersymbol(playerVolleyAutoFillMagazine)

registersymbol(playerPathHits)


// ============================================================
// ============================================================

FriendlyWeaponCoreData:

align 8

playerControlledWeaponBase:
dq 0


align 4

fireIntervalScale:
dd (float)1.0

oneFloat:
dd (float)1.0


//
playerVolleyCount:
dd 4


playerPathHits:
dd 0


// ============================================================
//
// ============================================================

weaponEnhanceMode:
db 1


playerVolleyEnabled:
db 0


//
playerVolleyAutoFillMagazine:
db 0



// ============================================================
//
//
//
// ============================================================

FriendlyWeaponCoreNewmem:

  pushfq
  push rax
  push rcx
  push rdx
  push r8

  lea rax,[rsi+rbx]

  mov qword ptr [playerControlledWeaponBase],rax
  inc dword ptr [playerPathHits]


  // ----------------------------------------------------------
  // ----------------------------------------------------------

  movzx ecx,byte ptr [weaponEnhanceMode]

  cmp ecx,1
  je FriendlyRateApplyRestore


  // ----------------------------------------------------------
  // ----------------------------------------------------------

  cmp ecx,2
  je FriendlyRateCheckPhysical

  cmp ecx,3
  je FriendlyRateCheckEnergy

  jmp FriendlyRateSkipRestore



FriendlyRateCheckPhysical:

  // ----------------------------------------------------------
  //
  // ----------------------------------------------------------

  mov rdx,rax
  shr rdx,4
  and edx,000003FF
  shl rdx,4

  mov r8,WeaponTypeCache
  add rdx,r8

  cmp qword ptr [rdx+00],rax
  jne FriendlyRateSkipRestore

  cmp byte ptr [rdx+08],01
  je FriendlyRateApplyRestore

  jmp FriendlyRateSkipRestore



FriendlyRateCheckEnergy:

  mov rdx,rax
  shr rdx,4
  and edx,000003FF
  shl rdx,4

  mov r8,WeaponTypeCache
  add rdx,r8

  cmp qword ptr [rdx+00],rax
  jne FriendlyRateSkipRestore

  cmp byte ptr [rdx+08],02
  je FriendlyRateApplyRestore

  jmp FriendlyRateSkipRestore



// ============================================================
// ============================================================

FriendlyRateApplyRestore:

  pop r8
  pop rdx
  pop rcx
  pop rax
  popfq

  vmovss xmm7,[oneFloat]
  vmovaps xmm6,xmm0

  vmulss xmm6,xmm6,[fireIntervalScale]

  jmp FriendlyWeaponCoreReturn



// ============================================================
// ============================================================

FriendlyRateSkipRestore:

  pop r8
  pop rdx
  pop rcx
  pop rax
  popfq

  vmovss xmm7,[oneFloat]
  vmovaps xmm6,xmm0

  jmp FriendlyWeaponCoreReturn



// ============================================================
// ============================================================

FriendlyWeaponCoreAOB+0A:
  jmp FriendlyWeaponCoreNewmem
  nop 7

FriendlyWeaponCoreReturn:



// ============================================================
//
// ============================================================

WeaponVolleyNewmem:

  pushfq
  push rax
  push rcx
  push rdx
  push r8

  sub rsp,10
  vmovdqu [rsp],xmm0


  lea rax,[r12+r14]

  test rax,rax
  jz WeaponVolleyFinish

  mov qword ptr [playerControlledWeaponBase],rax


  // ==========================================================
  // ==========================================================

  mov rdx,rax
  shr rdx,4
  and edx,000003FF
  shl rdx,4

  mov r8,WeaponTypeCache
  add rdx,r8

  mov qword ptr [rdx+00],rax



  test edi,edi
  jz WeaponVolleyCacheEnergy

  mov byte ptr [rdx+08],01
  jmp WeaponVolleyCacheDone


WeaponVolleyCacheEnergy:

  mov byte ptr [rdx+08],02


WeaponVolleyCacheDone:


  // ==========================================================
  // ==========================================================

  movzx ecx,byte ptr [weaponEnhanceMode]

  cmp ecx,1
  je WeaponVolleyModeAllowed

  cmp ecx,2
  je WeaponVolleyPhysicalOnly

  cmp ecx,3
  je WeaponVolleyEnergyOnly

  jmp WeaponVolleyFinish



WeaponVolleyPhysicalOnly:

  test edi,edi
  jnz WeaponVolleyModeAllowed

  jmp WeaponVolleyFinish



WeaponVolleyEnergyOnly:

  test edi,edi
  jz WeaponVolleyModeAllowed

  jmp WeaponVolleyFinish



// ============================================================
// ============================================================

WeaponVolleyModeAllowed:

  cmp byte ptr [playerVolleyEnabled],00
  je WeaponVolleyFinish


  // ==========================================================
  // ==========================================================

  mov ecx,[playerVolleyCount]

  cmp ecx,1
  jge WeaponVolleyMinimumOK

  mov ecx,1


WeaponVolleyMinimumOK:

  cmp ecx,32767
  jle WeaponVolleyMaximumOK

  mov ecx,32767


WeaponVolleyMaximumOK:


  // ==========================================================
  //
  // ==========================================================

  cmp byte ptr [playerVolleyAutoFillMagazine],00
  je WeaponVolleyNoAutoFill

  test edi,edi
  jz WeaponVolleyNoAutoFill

  cmp word ptr [rax+000002BE],cx
  jae WeaponVolleyNoAutoFill

  mov word ptr [rax+000002BE],cx


WeaponVolleyNoAutoFill:


  // ==========================================================
  //
  // ==========================================================

  dec ecx

  cvtsi2ss xmm0,ecx


  // ==========================================================
  //
  // ==========================================================

  vcomiss xmm0,[rax+00000254]
  jbe WeaponVolleyNoWrite

  vmovss [rax+00000254],xmm0


WeaponVolleyNoWrite:


WeaponVolleyFinish:

  vmovdqu xmm0,[rsp]
  add rsp,10

  pop r8
  pop rdx
  pop rcx
  pop rax
  popfq

  vmovss xmm1,[r12+r14+00000254]

  jmp WeaponVolleyReturn



// ============================================================
// ============================================================

StableVolleyAOB:
  jmp WeaponVolleyNewmem
  nop 5

WeaponVolleyReturn:



[DISABLE]

// ============================================================
// ============================================================

FriendlyWeaponCoreAOB+0A:
  db C5 FA 10 3D 84 8F 32 00 C5 F8 28 F0


// ============================================================
// ============================================================

StableVolleyAOB:
  db C4 81 7A 10 8C 34 54 02 00 00


// ============================================================
// ============================================================

unregistersymbol(playerPathHits)

unregistersymbol(playerVolleyAutoFillMagazine)
unregistersymbol(playerVolleyCount)
unregistersymbol(playerVolleyEnabled)

unregistersymbol(weaponEnhanceMode)
unregistersymbol(fireIntervalScale)
unregistersymbol(playerControlledWeaponBase)

unregistersymbol(StableVolleyAOB)
unregistersymbol(FriendlyWeaponCoreAOB)


// ============================================================
// ============================================================

dealloc(WeaponTypeCache)
dealloc(FriendlyWeaponCoreData)
dealloc(WeaponVolleyNewmem)
dealloc(FriendlyWeaponCoreNewmem)</AssemblerScript>
          <CheatEntries>
            <CheatEntry>
              <ID>80171</ID>
              <Description>"Weapon Enhancement Scope"</Description>
              <DropDownList ReadOnly="1" DescriptionOnly="1" DisplayValueAsItem="1">1:All weapons
2:Physical weapons only
3:Energy weapons only</DropDownList>
              <ShowAsSigned>0</ShowAsSigned>
              <VariableType>Byte</VariableType>
              <Address>weaponEnhanceMode</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>80130</ID>
              <Description>"Fire Interval Scale (lower = faster)"</Description>
              <DropDownList ReadOnly="1" DescriptionOnly="1" DisplayValueAsItem="1">1:Stock
0.5:2x fire rate
0.25:4x fire rate
0.1000000015:10x fire rate
0.05000000075:20x fire rate</DropDownList>
              <ShowAsSigned>0</ShowAsSigned>
              <VariableType>Float</VariableType>
              <Address>fireIntervalScale</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>80161</ID>
              <Description>"Player Weapon Volley"</Description>
              <DropDownList ReadOnly="1" DescriptionOnly="1" DisplayValueAsItem="1">0:Off
1:On</DropDownList>
              <ShowAsSigned>0</ShowAsSigned>
              <VariableType>Byte</VariableType>
              <Address>playerVolleyEnabled</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>80162</ID>
              <Description>"Total Shots per Trigger"</Description>
              <DropDownList ReadOnly="1" DescriptionOnly="1" DisplayValueAsItem="1">1:Stock single shot
2:2-shot volley
4:4-shot volley
10:10-shot volley</DropDownList>
              <ShowAsSigned>0</ShowAsSigned>
              <VariableType>4 Bytes</VariableType>
              <Address>playerVolleyCount</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>80163</ID>
              <Description>"Auto-fill Magazine Before Volley"</Description>
              <DropDownList ReadOnly="1" DescriptionOnly="1" DisplayValueAsItem="1">0:Off
1:On</DropDownList>
              <ShowAsSigned>0</ShowAsSigned>
              <VariableType>Byte</VariableType>
              <Address>playerVolleyAutoFillMagazine</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>99201</ID>
              <Description>"Advanced / Runtime Values (Debug)"</Description>
              <Options moHideChildren="1" />
              <GroupHeader>1</GroupHeader>
              <CheatEntries>
                <CheatEntry>
                  <ID>80160</ID>
                  <Description>"Current Weapon Instance"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <ShowAsSigned>0</ShowAsSigned>
                  <VariableType>8 Bytes</VariableType>
                  <Address>playerControlledWeaponBase</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>80159</ID>
                  <Description>"Fire-Rate Hook Hit Count"</Description>
                  <ShowAsSigned>0</ShowAsSigned>
                  <VariableType>4 Bytes</VariableType>
                  <Address>playerPathHits</Address>
                </CheatEntry>
              </CheatEntries>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>99305</ID>
          <Description>"Semi-Auto / Heavy Weapon Cooldown Unlock"</Description>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]

// ============================================================
//
//
//   movzx eax,word ptr [rsi+rdi+00000236]
//   test ax,ax
//   jle ...
//   dec ax
//   mov r12b,01
//   mov [rsi+rdi+00000236],ax
//
//
//
// ============================================================

aobscanmodule(SemiAutoCooldownAOB,HaloSimulation_tag_release.dll,0F B7 84 3E 36 02 00 00 66 85 C0 7E 0E 66 FF C8 41 B4 01 66 89 84 3E 36 02 00 00)
assert(SemiAutoCooldownAOB+0D,66 FF C8)

registersymbol(SemiAutoCooldownAOB)

// dec ax  -&gt;  xor ax,ax
SemiAutoCooldownAOB+0D:
  db 66 31 C0

[DISABLE]

SemiAutoCooldownAOB+0D:
  db 66 FF C8

unregistersymbol(SemiAutoCooldownAOB)</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>99306</ID>
          <Description>"+236 is the common post-shot cooldown tick; most visible on semi-auto and heavy weapons. AI shares the low-level path but usually remains limited by its own firing cadence."</Description>
          <GroupHeader>1</GroupHeader>
        </CheatEntry>
        <CheatEntry>
          <ID>99202</ID>
          <Description>"Fire-rate and volley controls use the previously tested Weapon Lab core; stale experimental spread rows and duplicate debug values were removed."</Description>
          <GroupHeader>1</GroupHeader>
        </CheatEntry>
        <CheatEntry>
          <ID>99300</ID>
          <Description>"Projectile Ballistics (scope: All; enable to scan current mission)"</Description>
          <Options moHideChildren="1" moDeactivateChildrenAsWell="1" />
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end

local PARENT_ID = 99300
local BASE_DESC = '"Projectile Ballistics (scope: All; enable to scan current mission)"'

local aliases = {
  rocket_launcher_rocket = "Rocket Launcher",
  plasma_pistol_bolt = "Plasma Pistol (Normal)",
  plasma_pistol_charged_bolt = "Plasma Pistol (Charged)",
  plasma_rifle_bolt = "Plasma Rifle",
  plasma_rifle_red_bolt = "Red Plasma Rifle",
  needler_shard = "Needler",
  needle_rifle_shard = "Needle Rifle",
  assault_rifle_bullet = "Assault Rifle",
  smg_bullet = "SMG",
  battle_rifle_bullet = "Battle Rifle",
  magnum_bullet = "Magnum",
  shotgun_bullet = "Shotgun",
  sniper_rifle_bullet = "Sniper Rifle",
  beam_rifle_beam = "Beam Rifle",
  spike_rifle_shard = "Spiker",
  hunter_fuel_rod_bolt = "Hunter Fuel Rod",
  fuel_rod_bolt = "Fuel Rod Projectile",
  fuel_rod_projectile = "Fuel Rod Projectile",
  fuel_rod_cannon_bolt = "Fuel Rod Projectile",
  flak_bolt = "Fuel Rod Gun",
  sentinel_gun_beam = "Sentinel Beam",
  seraph_quad_bomb_launcher_bomb = "Seraph Quad Bomb Launcher",
  seraph_quad_cannon_bolt = "Seraph Quad Cannon",
  space_banshee_dual_cannon_bolt = "Space Banshee Dual Cannon",
  pelican_chin_gun_bullet = "Pelican Chin Gun",
  concussion_rifle_grenade = "Concussion Rifle Grenade",
  plasma_launcher_grenade = "Plasma Launcher Grenade",
  ag_turret_two = "Covenant Corvette Turret",
  frigate_turret_round = "UNSC Frigate Turret",
  ghost_dual_cannon_bolt = "Ghost Dual Plasma Cannon",
  shade_plasma_cannon_bolt = "Shade Plasma Cannon",
  phantom_chin_gun = "Phantom Chin Gun",
  phantom_chin_gun_projectile = "Phantom Chin Gun",
  phantom_chin_gun_round = "Phantom Chin Gun",
  warthog_rocket_pod_rocket = "Warthog Rocket Pod",
  warthog_rocket_pod_projectile = "Warthog Rocket Pod",
  frag_grenade = "Frag Grenade",
  plasma_grenade = "Plasma Grenade"
}

local exactPathAliases = {
  ["objects\\vehicles\\human\\scorpion\\turrets\\scorpion_anti_infantry\\weapon\\projectiles\\scorpion_anti_infantry_bullet"] = "Scorpion Machine Gun",
  ["objects\\vehicles\\human\\scorpion\\turrets\\scorpion_cannon\\weapon\\projectiles\\scorpion_cannon_round"] = "Scorpion Main Cannon",
  ["objects\\vehicles\\covenant\\wraith\\turrets\\wraith_anti_infantry\\weapon\\projectiles\\wraith_anti_infantry_bolt"] = "Wraith Machine Gun",
  ["objects\\vehicles\\covenant\\wraith\\turrets\\wraith_mortar\\weapon\\projectiles\\wraith_mortar_round"] = "Wraith Mortar"
}

local function leafName(path)
  if not path then return "" end
  if exactPathAliases[path] then return exactPathAliases[path] end
  local leaf = path:match("([^\\]+)$") or path
  if aliases[leaf] then return aliases[leaf] end
  local lower = string.lower(leaf)
  if lower:find("banshee",1,true) and (lower:find("bomb",1,true) or lower:find("missile",1,true)) then return "Banshee Missile" end
  if lower:find("phantom",1,true) and lower:find("chin_gun",1,true) then return "Phantom Chin Gun" end
  if lower:find("warthog",1,true) and lower:find("rocket",1,true) then return "Warthog Rocket Pod" end
  if lower:find("warthog",1,true) then return "Warthog Weapon" end
  if lower:find("phantom",1,true) then return "Phantom Weapon" end
  if lower:find("seraph",1,true) then return "Seraph Weapon" end
  if lower:find("corvette",1,true) then return "Covenant Corvette Weapon" end
  if lower:find("frigate",1,true) then return "UNSC Frigate Weapon" end
  if lower:find("ghost",1,true) then return "Ghost Weapon" end
  if lower:find("shade",1,true) then return "Shade Turret Weapon" end
  if lower:find("fuel_rod",1,true) then return "Fuel Rod Projectile" end
  if lower:find("banshee",1,true) then return "Banshee Weapon" end
  return (leaf:gsub("_"," "))
end

local function isSpecial(path)
  local p = string.lower(path or "")
  if p:find("objects\\vehicles\\",1,true) == 1 then return true end
  if p:find("objects\\characters\\",1,true) == 1 then return true end
  if p:find("\\flak_cannon\\",1,true) then return true end
  if p:find("\\sentinel_gun\\",1,true) then return true end
  if p:find("\\turret\\",1,true) then return true end
  if p:find("\\pelican_",1,true) then return true end
  return false
end

local function isConventionalBullet(path)
  local leaf = string.lower((path or ""):match("([^\\]+)$") or "")
  return leaf == "assault_rifle_bullet"
      or leaf == "smg_bullet"
      or leaf == "battle_rifle_bullet"
      or leaf == "magnum_bullet"
      or leaf == "shotgun_bullet"
      or leaf == "sniper_rifle_bullet"
end

local function getCore()
  local lastErr = "TagSystem is not ready"
  for attempt=1,30 do
    local m = getAddressSafe("HaloSimulation_tag_release.dll")
    if m ~= nil then
      local tagSystem = readQword(m + 0x182D1E8)
      if tagSystem ~= nil and tagSystem ~= 0 then
        local entryBase = readQword(tagSystem + 0x50)
        if entryBase ~= nil and entryBase ~= 0 then
          return {
            m=m,
            tagSystem=tagSystem,
            entryBase=entryBase,
            shardTable=m+0x2C2CCC0
          }, nil
        end
        lastErr = "Tag Entry table is not ready"
      else
        lastErr = "TagSystem is not ready"
      end
    else
      lastErr = "HaloSimulation_tag_release.dll not found"
    end
    if attempt &lt; 30 then sleep(100) end
  end
  return nil, lastErr.." (after a mission change, wait 1-2 seconds and enable again)"
end

local function resolveMeta(core, entry)
  local packed = readInteger(entry + 0x1C)
  if packed == nil then return nil end
  packed = packed &amp; 0xFFFFFFFF
  local shardIndex = (packed &gt;&gt; 28) &amp; 0xF
  local shardPtr = readQword(core.shardTable + shardIndex*8)
  if shardPtr == nil or shardPtr == 0 then return nil end
  return shardPtr + packed*4
end

local function resolvePacked(core, packed)
  if packed == nil then return nil end
  packed = packed &amp; 0xFFFFFFFF
  local shardIndex = (packed &gt;&gt; 28) &amp; 0xF
  local shardPtr = readQword(core.shardTable + shardIndex*8)
  if shardPtr == nil or shardPtr == 0 then return nil end
  return shardPtr + packed*4
end

local function cleanupState(state, restore)
  if state == nil then return end
  if restore and state.items and state.core then
    for _,p in ipairs(state.items) do
      pcall(function()
        local entry = state.core.entryBase + p.index*0x30
        local group = readInteger(entry + 0x04)
        local header = readInteger(entry + 0x00)
        local namePtr = readQword(entry + 0x10)
        if group == 0x70726F6A and header ~= nil and namePtr ~= nil and namePtr ~= 0 then
          local salt = header &amp; 0xFFFF
          local datum = ((salt &lt;&lt; 16) | p.index) &amp; 0xFFFFFFFF
          local path = readString(namePtr,512,false)
          local meta = resolveMeta(state.core, entry)
          if datum == p.datum and path == p.path and meta == p.meta then
            writeFloat(meta + 0x174, p.range)
            writeFloat(meta + 0x2B4, p.speed1)
            writeFloat(meta + 0x2B8, p.speed2)
            if p.shotgunSpread ~= nil then
              local blockCount = readInteger(meta + 0x328)
              local blockPacked = readInteger(meta + 0x32C)
              local block = resolvePacked(state.core, blockPacked)
              if blockCount ~= nil and blockCount &gt; 0 and block == p.shotgunSpread.block then
                writeInteger(block + 0x00, p.shotgunSpread.gridPacked)
                writeFloat(block + 0x08, p.shotgunSpread.angle)
              end
            end
          end
        end
      end)
    end
  end
  if state.records then
    for i=#state.records,1,-1 do
      pcall(function() state.records[i].destroy() end)
    end
  end
end

-- Remove a stale runtime tree from an interrupted/reloaded level/table activation.
-- IMPORTANT: stale state may point at the previous level's Tag table.
-- Do NOT restore through that old core here; just destroy the generated records.
-- Normal manual disable still restores the current level safely in [DISABLE].
if _G.HaloProjectileBallisticsState ~= nil then
  cleanupState(_G.HaloProjectileBallisticsState, false)
  _G.HaloProjectileBallisticsState = nil
end

local core, err = getCore()
if core == nil then error(err) end

local list = {}
for index=0,0xFFFF do
  local entry = core.entryBase + index*0x30
  local group = readInteger(entry+0x04)
  if group == 0x70726F6A then -- proj
    local header = readInteger(entry+0x00)
    local namePtr = readQword(entry+0x10)
    if header ~= nil and namePtr ~= nil and namePtr ~= 0 then
      local path = readString(namePtr,512,false)
      local meta = resolveMeta(core, entry)
      if path ~= nil and path ~= "" and meta ~= nil and meta ~= 0 then
        local r = readFloat(meta+0x174)
        local s1 = readFloat(meta+0x2B4)
        local s2 = readFloat(meta+0x2B8)
        if r ~= nil and s1 ~= nil and s2 ~= nil then
          local salt = header &amp; 0xFFFF
          local datum = ((salt &lt;&lt; 16) | index) &amp; 0xFFFFFFFF
          local item = {
            index=index, datum=datum, path=path, meta=meta,
            name=leafName(path), special=isSpecial(path),
            conventionalBullet=isConventionalBullet(path),
            range=r, speed1=s1, speed2=s2
          }
          local leaf = string.lower(path:match("([^\\]+)$") or "")
          if leaf == "shotgun_bullet" then
            local blockCount = readInteger(meta + 0x328)
            local blockPacked = readInteger(meta + 0x32C)
            local block = resolvePacked(core, blockPacked)
            if blockCount ~= nil and blockCount &gt; 0 and block ~= nil and block ~= 0 then
              local gridPacked = readInteger(block + 0x00)
              local angle = readFloat(block + 0x08)
              if gridPacked ~= nil and angle ~= nil then
                gridPacked = gridPacked &amp; 0xFFFFFFFF
                item.shotgunSpread = {
                  block=block,
                  gridPacked=gridPacked,
                  gridX=gridPacked &amp; 0xFFFF,
                  gridY=(gridPacked &gt;&gt; 16) &amp; 0xFFFF,
                  angle=angle
                }
              end
            end
          end
          list[#list+1] = item
        end
      end
    end
  end
end

table.sort(list,function(a,b)
  if a.special ~= b.special then return not a.special end
  local an=string.lower(a.name or "")
  local bn=string.lower(b.name or "")
  if an ~= bn then return an &lt; bn end
  return string.lower(a.path) &lt; string.lower(b.path)
end)

local al = getAddressList()
local parent = al.getMemoryRecordByID(PARENT_ID)
if parent == nil then error("Projectile Ballistics parent entry not found") end

local state = {records={}, items=list, core=core}
_G.HaloProjectileBallisticsState = state

local function remember(mr)
  mr.DontSave = true
  state.records[#state.records+1] = mr
  return mr
end

local function addFloat(group, desc, addr)
  local mr = remember(al.createMemoryRecord())
  mr.Description = desc
  mr.Type = vtSingle
  mr.Address = string.format("%X", addr)
  mr.appendToEntry(group)
  return mr
end

local function addDropDown(group, desc, addr, vartype, items)
  local mr = remember(al.createMemoryRecord())
  mr.Description = desc
  mr.Type = vartype
  mr.Address = string.format("%X", addr)
  mr.DropDownReadOnly = true
  mr.DropDownDescriptionOnly = true
  mr.DisplayAsDropDownListItem = true
  local dd = mr.DropDownList
  dd.clear()
  for _,line in ipairs(items) do dd.add(line) end
  mr.appendToEntry(group)
  return mr
end

for _,p in ipairs(list) do
  local g = remember(al.createMemoryRecord())
  local marks = {}
  if p.special then marks[#marks+1] = "🟨" end
  if p.conventionalBullet then marks[#marks+1] = "⚠" end
  local mark = (#marks &gt; 0) and (table.concat(marks, "").." ") or ""
  g.Description = string.format("%s%s  [%08X]", mark, p.name, p.datum)
  g.IsGroupHeader = true
  g.Options = "[moHideChildren]"
  g.appendToEntry(parent)

  addFloat(g, string.format("Maximum Travel Distance (+174)   [Stock %.6g]", p.range), p.meta+0x174)
  if p.conventionalBullet then
    addFloat(g, string.format("⚠ Stage 1 Speed / Initial Velocity (+2B4)   [Stock %.6g; special ballistic path, not currently a reliable editable field]", p.speed1), p.meta+0x2B4)
    addFloat(g, string.format("⚠ Stage 2 Speed / Decay Velocity (+2B8)   [Stock %.6g; special ballistic path, not currently a reliable editable field]", p.speed2), p.meta+0x2B8)
  else
    addFloat(g, string.format("Stage 1 Speed / Initial Velocity (+2B4)   [Stock %.6g]", p.speed1), p.meta+0x2B4)
    addFloat(g, string.format("Stage 2 Speed / Decay Velocity (+2B8)   [Stock %.6g]", p.speed2), p.meta+0x2B8)
  end

  if p.shotgunSpread ~= nil then
    local sg = p.shotgunSpread
    local normalAngle = string.format("%.9g", sg.angle)
    addDropDown(
      g,
      string.format("Shotgun Spread Angle (Tight / Stock / Wide)   [Stock %.6g ≈ 9°]", sg.angle),
      sg.block + 0x08,
      vtSingle,
      {
        "0.01:Tight (~0.57°)",
        normalAngle..":Stock (~9°)",
        "0.5:Wide (~28.65°)"
      }
    )

    local total = sg.gridX * sg.gridY
    local originalPacked = tostring(sg.gridPacked)
    local entries = {}
    local seen = {}
    local function addGrid(value, label)
      local key=tostring(value)
      if not seen[key] then
        entries[#entries+1]=key..":"..label
        seen[key]=true
      end
    end
    addGrid(sg.gridPacked, string.format("Stock %d pellets (%dx%d)", total, sg.gridX, sg.gridY))
    addGrid(0x00010001, "1 pellet (1x1; debug)")
    addGrid(0x00050005, "25 pellets (5x5)")
    addGrid(0x000A000A, "100 pellets (10x10; high load)")
    addGrid(0x00140014, "400 pellets (20x20; extreme load)")
    addDropDown(
      g,
      "Shotgun Logical Pellet Count (2D grid XxY; visual effects may be engine-limited)",
      sg.block + 0x00,
      vtDword,
      entries
    )
  end
end

parent.Description = string.format('"Projectile Ballistics (All; scanned %d projectiles)"', #list)
{$asm}

[DISABLE]
{$lua}
if syntaxcheck then return end
local state = _G.HaloProjectileBallisticsState
if state ~= nil then
  -- Only restore when the same current-level datum/path/meta is still present.
  if state.items and state.core then
    local function resolveMeta(core, entry)
      local packed = readInteger(entry + 0x1C)
      if packed == nil then return nil end
      packed = packed &amp; 0xFFFFFFFF
      local shardIndex = (packed &gt;&gt; 28) &amp; 0xF
      local shardPtr = readQword(core.shardTable + shardIndex*8)
      if shardPtr == nil or shardPtr == 0 then return nil end
      return shardPtr + packed*4
    end
    local function resolvePacked(core, packed)
      if packed == nil then return nil end
      packed = packed &amp; 0xFFFFFFFF
      local shardIndex = (packed &gt;&gt; 28) &amp; 0xF
      local shardPtr = readQword(core.shardTable + shardIndex*8)
      if shardPtr == nil or shardPtr == 0 then return nil end
      return shardPtr + packed*4
    end
    for _,p in ipairs(state.items) do
      pcall(function()
        local entry = state.core.entryBase + p.index*0x30
        local group = readInteger(entry + 0x04)
        local header = readInteger(entry + 0x00)
        local namePtr = readQword(entry + 0x10)
        if group == 0x70726F6A and header ~= nil and namePtr ~= nil and namePtr ~= 0 then
          local salt = header &amp; 0xFFFF
          local datum = ((salt &lt;&lt; 16) | p.index) &amp; 0xFFFFFFFF
          local path = readString(namePtr,512,false)
          local meta = resolveMeta(state.core, entry)
          if datum == p.datum and path == p.path and meta == p.meta then
            writeFloat(meta + 0x174, p.range)
            writeFloat(meta + 0x2B4, p.speed1)
            writeFloat(meta + 0x2B8, p.speed2)
            if p.shotgunSpread ~= nil then
              local blockCount = readInteger(meta + 0x328)
              local blockPacked = readInteger(meta + 0x32C)
              local block = resolvePacked(state.core, blockPacked)
              if blockCount ~= nil and blockCount &gt; 0 and block == p.shotgunSpread.block then
                writeInteger(block + 0x00, p.shotgunSpread.gridPacked)
                writeFloat(block + 0x08, p.shotgunSpread.angle)
              end
            end
          end
        end
      end)
    end
  end
  if state.records then
    for i=#state.records,1,-1 do
      pcall(function() state.records[i].destroy() end)
    end
  end
end
_G.HaloProjectileBallisticsState = nil
local p = getAddressList().getMemoryRecordByID(99300)
if p ~= nil then p.Description = '"Projectile Ballistics (scope: All; enable to scan current mission)"' end
{$asm}</AssemblerScript>
          <CheatEntries>
            <CheatEntry>
              <ID>99301</ID>
              <Description>"Energy Sword Lunge Lock Distance = 5 (red-reticle range; does not bypass the hard lunge limit)"</Description>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end

local PATH = "objects\\weapons\\melee\\energy_sword\\energy_sword"
local GROUP = 0x77656170 -- weap

local function getCore()
  local m = getAddressSafe("HaloSimulation_tag_release.dll")
  if m == nil then return nil end
  local tagSystem = readQword(m + 0x182D1E8)
  if tagSystem == nil or tagSystem == 0 then return nil end
  local entryBase = readQword(tagSystem + 0x50)
  if entryBase == nil or entryBase == 0 then return nil end
  return {m=m, entryBase=entryBase, shardTable=m+0x2C2CCC0}
end

local function resolveMeta(core, entry)
  local packed = readInteger(entry + 0x1C)
  if packed == nil then return nil end
  packed = packed &amp; 0xFFFFFFFF
  local shardIndex = (packed &gt;&gt; 28) &amp; 0xF
  local shardPtr = readQword(core.shardTable + shardIndex*8)
  if shardPtr == nil or shardPtr == 0 then return nil end
  return shardPtr + packed*4
end

local function restoreOld()
  local old = _G.HaloSwordLungeLockState
  if old ~= nil and old.meta ~= nil then
    pcall(function()
      if readFloat(old.meta+0x2CC) ~= nil then writeFloat(old.meta+0x2CC, old.a) end
      if readFloat(old.meta+0x2D0) ~= nil then writeFloat(old.meta+0x2D0, old.b) end
    end)
  end
  _G.HaloSwordLungeLockState = nil
end

restoreOld()
local core = getCore()
if core == nil then error("TagSystem is not ready") end

local found = nil
for index=0,0xFFFF do
  local entry = core.entryBase + index*0x30
  if readInteger(entry+0x04) == GROUP then
    local namePtr = readQword(entry+0x10)
    if namePtr ~= nil and namePtr ~= 0 then
      local path = readString(namePtr,512,false)
      if path == PATH then
        local meta = resolveMeta(core, entry)
        if meta ~= nil and meta ~= 0 then
          found = meta
          break
        end
      end
    end
  end
end

if found == nil then error("Energy Sword WEAP was not found in the current mission") end
local a = readFloat(found+0x2CC)
local b = readFloat(found+0x2D0)
if a == nil or b == nil then error("Unable to read Energy Sword lunge-lock fields") end

_G.HaloSwordLungeLockState = {meta=found, a=a, b=b}
writeFloat(found+0x2CC, 5.0)
writeFloat(found+0x2D0, 5.0)
{$asm}

[DISABLE]
{$lua}
if syntaxcheck then return end
local s = _G.HaloSwordLungeLockState
if s ~= nil and s.meta ~= nil then
  pcall(function()
    if readFloat(s.meta+0x2CC) ~= nil then writeFloat(s.meta+0x2CC, s.a) end
    if readFloat(s.meta+0x2D0) ~= nil then writeFloat(s.meta+0x2D0, s.b) end
  end)
end
_G.HaloSwordLungeLockState = nil
{$asm}</AssemblerScript>
            </CheatEntry>
            <CheatEntry NoCheckbox="1">
              <ID>99302</ID>
              <Description>"Ballistics dynamically scans projectile tags loaded in the current mission. v1.0t fixes reopening after mission changes. If the parent remains checked after a transition, uncheck and re-enable it to rescan. Shotgun entries also expose spread and logical pellet count."</Description>
              <GroupHeader>1</GroupHeader>
            </CheatEntry>
            <CheatEntry NoCheckbox="1">
              <ID>99303</ID>
              <Description>"Markers: 🟨 = vehicle/NPC/special projectile; ⚠ = special ballistic path. Shotgun presets expose spread (0.01 / stock ~9° / 0.5) and logical pellet count; 100/400-pellet settings increase engine load."</Description>
              <GroupHeader>1</GroupHeader>
            </CheatEntry>
            <CheatEntry NoCheckbox="1">
              <ID>99304</ID>
              <Description>"Scope: Ballistics edits current-mission projectile Tag Meta directly, so only All is currently meaningful. Projectile Armory P1 filtering cannot be safely reused here until a runtime speed-read path is found."</Description>
              <GroupHeader>1</GroupHeader>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>99000</ID>
          <Description>"Projectile Armory v1.0s -- 3-Preset Projectile Replacement"</Description>
          <Options moHideChildren="1" moDeactivateChildrenAsWell="1" />
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
aobscanmodule(projectileArmoryHookAOB,HaloSimulation_tag_release.dll,43 8B B4 A6 14 01 00 00)
alloc(projectileArmoryOriginal,8,projectileArmoryHookAOB)
registersymbol(projectileArmoryOriginal)
projectileArmoryOriginal:
  readmem(projectileArmoryHookAOB,8)
alloc(projectileArmoryNewmem,$1800,projectileArmoryHookAOB)
alloc(projectileArmoryData,$100,projectileArmoryHookAOB)
label(projectileArmoryReturn)
label(projectileArmoryReturnAddress)
label(projectileArmoryCheckSlots)
label(projectileArmoryNotP1)
label(projectileArmoryP1Matched)
label(projectileSwapEnable)
label(projectileScopeMode)
label(projectileSlot1Enable)
label(projectileSlot2Enable)
label(projectileSlot3Enable)
label(projectileSourceTag1)
label(projectileTargetTag1)
label(projectileSourceTag2)
label(projectileTargetTag2)
label(projectileSourceTag3)
label(projectileTargetTag3)
label(projectileSourceTag)
label(projectileTargetTag)
registersymbol(projectileArmoryHookAOB)
registersymbol(projectileSwapEnable)
registersymbol(projectileScopeMode)
registersymbol(projectileSlot1Enable)
registersymbol(projectileSlot2Enable)
registersymbol(projectileSlot3Enable)
registersymbol(projectileSourceTag1)
registersymbol(projectileTargetTag1)
registersymbol(projectileSourceTag2)
registersymbol(projectileTargetTag2)
registersymbol(projectileSourceTag3)
registersymbol(projectileTargetTag3)
registersymbol(projectileSourceTag)
registersymbol(projectileTargetTag)

projectileArmoryData:
projectileSwapEnable:
  db 0
projectileScopeMode:
  db 0
projectileSlot1Enable:
  db 0
projectileSlot2Enable:
  db 0
projectileSlot3Enable:
  db 0
  db 0,0,0
align 4
projectileSourceTag1:
projectileSourceTag:
  dd FFFFFFFF
projectileTargetTag1:
projectileTargetTag:
  dd FFFFFFFF
projectileSourceTag2:
  dd FFFFFFFF
projectileTargetTag2:
  dd FFFFFFFF
projectileSourceTag3:
  dd FFFFFFFF
projectileTargetTag3:
  dd FFFFFFFF

projectileArmoryNewmem:
  // v1.0s: common upstream projectile-selection hook + shotgun spread presets; covers conventional ballistic and standard projectile paths.
  // Preserve the original instruction first so ESI contains the stock source projectile datum.
  mov esi,[r14+r12*4+00000114]

  cmp byte ptr [projectileSwapEnable],0
  je projectileArmoryReturn

  cmp byte ptr [projectileScopeMode],1
  je projectileArmoryCheckSlots

  // P1 filter adapted from the original author's hook at this exact code site.
  // It accepts either the P1 controlled object itself or P1's currently held weapon object.
  pushfq
  push rax
  push rcx
  push r10
  push r11

  mov eax,[rsp+B0]
  cmp eax,-1
  je projectileArmoryNotP1
  and eax,0000FFFF

  mov rcx,[r15+20]
  test rcx,rcx
  jz projectileArmoryNotP1
  mov r11,[rcx+50]
  test r11,r11
  jz projectileArmoryNotP1
  cmp eax,[rcx+2C]
  jae projectileArmoryNotP1
  lea r10,[rax+rax*2]
  mov r10,[r11+r10*8+10]
  test r10,r10
  jz projectileArmoryNotP1
  cmp r10,10000
  jb projectileArmoryNotP1
  mov rcx,r10

  mov r10,[r15+30]
  test r10,r10
  jz projectileArmoryNotP1
  mov r10,[r10+50]
  test r10,r10
  jz projectileArmoryNotP1
  mov eax,[r10+28]
  cmp eax,-1
  je projectileArmoryNotP1
  and eax,0000FFFF
  mov r10,[r15+20]
  cmp eax,[r10+2C]
  jae projectileArmoryNotP1
  lea r10,[rax+rax*2]
  mov r10,[r11+r10*8+10]
  test r10,r10
  jz projectileArmoryNotP1
  cmp rcx,r10
  je projectileArmoryP1Matched

  movsx rax,byte ptr [r10+33E]
  cmp rax,0
  jl projectileArmoryNotP1
  cmp rax,3
  jg projectileArmoryNotP1
  lea r10,[r10+344]
  mov eax,[r10+rax*4]
  cmp eax,-1
  je projectileArmoryNotP1
  and eax,0000FFFF
  mov r10,[r15+20]
  cmp eax,[r10+2C]
  jae projectileArmoryNotP1
  lea r10,[rax+rax*2]
  mov rax,[r11+r10*8+10]
  test rax,rax
  jz projectileArmoryNotP1
  cmp rcx,rax
  jne projectileArmoryNotP1

projectileArmoryP1Matched:
  pop r11
  pop r10
  pop rcx
  pop rax
  popfq
  jmp projectileArmoryCheckSlots

projectileArmoryNotP1:
  pop r11
  pop r10
  pop rcx
  pop rax
  popfq
  jmp projectileArmoryReturn

projectileArmoryCheckSlots:
  cmp byte ptr [projectileSlot1Enable],0
  je @f
  cmp esi,[projectileSourceTag1]
  jne @f
  mov esi,[projectileTargetTag1]
  jmp projectileArmoryReturn
@@:
  cmp byte ptr [projectileSlot2Enable],0
  je @f
  cmp esi,[projectileSourceTag2]
  jne @f
  mov esi,[projectileTargetTag2]
  jmp projectileArmoryReturn
@@:
  cmp byte ptr [projectileSlot3Enable],0
  je projectileArmoryReturn
  cmp esi,[projectileSourceTag3]
  jne projectileArmoryReturn
  mov esi,[projectileTargetTag3]

projectileArmoryReturn:
  jmp projectileArmoryReturnAddress

projectileArmoryHookAOB:
  jmp projectileArmoryNewmem
  nop 3
projectileArmoryReturnAddress:

[DISABLE]
projectileArmoryHookAOB:
  readmem(projectileArmoryOriginal,8)

unregistersymbol(projectileTargetTag)
unregistersymbol(projectileSourceTag)
unregistersymbol(projectileTargetTag3)
unregistersymbol(projectileSourceTag3)
unregistersymbol(projectileTargetTag2)
unregistersymbol(projectileSourceTag2)
unregistersymbol(projectileTargetTag1)
unregistersymbol(projectileSourceTag1)
unregistersymbol(projectileSlot3Enable)
unregistersymbol(projectileSlot2Enable)
unregistersymbol(projectileSlot1Enable)
unregistersymbol(projectileScopeMode)
unregistersymbol(projectileSwapEnable)
unregistersymbol(projectileArmoryHookAOB)
unregistersymbol(projectileArmoryOriginal)
dealloc(projectileArmoryData)
dealloc(projectileArmoryNewmem)
dealloc(projectileArmoryOriginal)</AssemblerScript>
          <CheatEntries>
            <CheatEntry>
              <ID>99001</ID>
              <Description>"Open / Close Projectile Armory Console"</Description>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end

-- Close an older copy of the panel if it exists.
if _G.HALO_PROJECTILE_ARMORY_FORM ~= nil then
  pcall(function() _G.HALO_PROJECTILE_ARMORY_FORM.close() end)
  pcall(function() _G.HALO_PROJECTILE_ARMORY_FORM.destroy() end)
  _G.HALO_PROJECTILE_ARMORY_FORM = nil
end

local ROCKET_PATH = "objects\\weapons\\support_high\\rocket_launcher\\projectiles\\rocket_launcher_rocket"
local DEFAULT_SOURCE = {
  "objects\\weapons\\pistol\\plasma_pistol\\projectiles\\plasma_pistol_bolt",
  "objects\\weapons\\rifle\\plasma_rifle\\projectiles\\plasma_rifle_bolt",
  "objects\\weapons\\pistol\\needler\\projectiles\\needler_shard"
}

local aliases = {
  rocket_launcher_rocket = "Rocket Launcher",
  plasma_pistol_bolt = "Plasma Pistol (Normal)",
  plasma_pistol_charged_bolt = "Plasma Pistol (Charged)",
  plasma_rifle_bolt = "Plasma Rifle",
  plasma_rifle_red_bolt = "Red Plasma Rifle",
  needler_shard = "Needler",
  needle_rifle_shard = "Needle Rifle",
  assault_rifle_bullet = "Assault Rifle",
  smg_bullet = "SMG",
  battle_rifle_bullet = "Battle Rifle",
  magnum_bullet = "Magnum",
  shotgun_bullet = "Shotgun",
  sniper_rifle_bullet = "Sniper Rifle",
  beam_rifle_beam = "Beam Rifle",
  spike_rifle_shard = "Spiker",
  hunter_fuel_rod_bolt = "Hunter Fuel Rod",
  fuel_rod_bolt = "Fuel Rod Projectile",
  fuel_rod_projectile = "Fuel Rod Projectile",
  fuel_rod_cannon_bolt = "Fuel Rod Projectile",
  flak_bolt = "Fuel Rod Gun",
  sentinel_gun_beam = "Sentinel Beam",
  seraph_quad_bomb_launcher_bomb = "Seraph Quad Bomb Launcher",
  seraph_quad_cannon_bolt = "Seraph Quad Cannon",
  space_banshee_dual_cannon_bolt = "Space Banshee Dual Cannon",
  pelican_chin_gun_bullet = "Pelican Chin Gun",
  concussion_rifle_grenade = "Concussion Rifle Grenade",
  plasma_launcher_grenade = "Plasma Launcher Grenade",
  ag_turret_two = "Covenant Corvette Turret",
  frigate_turret_round = "UNSC Frigate Turret",
  ghost_dual_cannon_bolt = "Ghost Dual Plasma Cannon",
  shade_plasma_cannon_bolt = "Shade Plasma Cannon",
  phantom_chin_gun = "Phantom Chin Gun",
  phantom_chin_gun_projectile = "Phantom Chin Gun",
  phantom_chin_gun_round = "Phantom Chin Gun",
  warthog_rocket_pod_rocket = "Warthog Rocket Pod",
  warthog_rocket_pod_projectile = "Warthog Rocket Pod",
  frag_grenade = "Frag Grenade",
  plasma_grenade = "Plasma Grenade"
}

local exactPathAliases = {
  ["objects\\vehicles\\human\\scorpion\\turrets\\scorpion_anti_infantry\\weapon\\projectiles\\scorpion_anti_infantry_bullet"] = "Scorpion Machine Gun",
  ["objects\\vehicles\\human\\scorpion\\turrets\\scorpion_cannon\\weapon\\projectiles\\scorpion_cannon_round"] = "Scorpion Main Cannon",
  ["objects\\vehicles\\covenant\\wraith\\turrets\\wraith_anti_infantry\\weapon\\projectiles\\wraith_anti_infantry_bolt"] = "Wraith Machine Gun",
  ["objects\\vehicles\\covenant\\wraith\\turrets\\wraith_mortar\\weapon\\projectiles\\wraith_mortar_round"] = "Wraith Mortar"
}

local function leafName(path)
  if not path then return "" end
  if exactPathAliases[path] then return exactPathAliases[path] end
  local leaf = path:match("([^\\]+)$") or path
  if aliases[leaf] then return aliases[leaf] end
  local lower = string.lower(leaf)
  if lower:find("banshee",1,true) and (lower:find("bomb",1,true) or lower:find("missile",1,true)) then
    return "Banshee Missile"
  end
  if lower:find("phantom",1,true) and lower:find("chin_gun",1,true) then
    return "Phantom Chin Gun"
  end
  if lower:find("warthog",1,true) and lower:find("rocket",1,true) then
    return "Warthog Rocket Pod"
  end
  if lower:find("warthog",1,true) then return "Warthog Weapon" end
  if lower:find("phantom",1,true) then return "Phantom Weapon" end
  if lower:find("seraph",1,true) then return "Seraph Weapon" end
  if lower:find("corvette",1,true) then return "Covenant Corvette Weapon" end
  if lower:find("frigate",1,true) then return "UNSC Frigate Weapon" end
  if lower:find("ghost",1,true) then return "Ghost Weapon" end
  if lower:find("shade",1,true) then return "Shade Turret Weapon" end
  if lower:find("fuel_rod",1,true) then return "Fuel Rod Projectile" end
  if lower:find("banshee",1,true) then return "Banshee Weapon" end
  return (leaf:gsub("_"," "))
end

local function hasTranslation(path)
  if not path then return false end
  if exactPathAliases[path] then return true end
  local leaf = path:match("([^\\]+)$") or path
  if aliases[leaf] then return true end
  local lower = string.lower(leaf)
  if lower:find("banshee",1,true) and (lower:find("bomb",1,true) or lower:find("missile",1,true)) then return true end
  if lower:find("phantom",1,true) and lower:find("chin_gun",1,true) then return true end
  if lower:find("warthog",1,true) and lower:find("rocket",1,true) then return true end
  if lower:find("warthog",1,true) then return true end
  if lower:find("phantom",1,true) then return true end
  if lower:find("seraph",1,true) then return true end
  if lower:find("corvette",1,true) then return true end
  if lower:find("frigate",1,true) then return true end
  if lower:find("ghost",1,true) then return true end
  if lower:find("shade",1,true) then return true end
  if lower:find("fuel_rod",1,true) then return true end
  if lower:find("banshee",1,true) then return true end
  return false
end

local function isNormalPlayerProjectile(path)
  local p = string.lower(path or "")
  if p:find("objects\\vehicles\\",1,true) == 1 then return false end
  if p:find("objects\\characters\\",1,true) == 1 then return false end
  if p:find("\\flak_cannon\\",1,true) then return false end
  if p:find("\\sentinel_gun\\",1,true) then return false end
  if p:find("\\turret\\",1,true) then return false end
  if p:find("\\pelican_",1,true) then return false end
  if p:find("objects\\weapons\\",1,true) == 1 then return true end
  return false
end

local function getEntryBase()
  local moduleBase = getAddressSafe("HaloSimulation_tag_release.dll")
  if moduleBase == nil then return nil, "HaloSimulation_tag_release.dll not found" end
  local tagSystem = readQword(moduleBase + 0x182D1E8)
  if tagSystem == nil or tagSystem == 0 then return nil, "TagSystem is not ready" end
  local entryBase = readQword(tagSystem + 0x50)
  if entryBase == nil or entryBase == 0 then return nil, "Tag Entry table is not ready" end
  return entryBase
end

local function scanProjectiles()
  local entryBase, err = getEntryBase()
  if entryBase == nil then return nil, err end
  local result = {}
  for index=0,0xFFFF do
    local entry = entryBase + index*0x30
    local group = readInteger(entry+0x04)
    if group == 0x70726F6A then -- proj
      local header = readInteger(entry+0x00)
      local namePtr = readQword(entry+0x10)
      if header ~= nil and namePtr ~= nil and namePtr ~= 0 then
        local path = readString(namePtr,512,false)
        if path ~= nil and path ~= "" then
          local salt = header &amp; 0xFFFF
          local datum = ((salt &lt;&lt; 16) | index) &amp; 0xFFFFFFFF
          result[#result+1] = {
            path=path,
            datum=datum,
            index=index,
            special=not isNormalPlayerProjectile(path),
            name=leafName(path),
            translated=hasTranslation(path)
          }
        end
      end
    end
  end
  table.sort(result,function(a,b)
    if a.special ~= b.special then return not a.special end
    return string.lower(a.name) &lt; string.lower(b.name)
  end)
  return result,nil
end

local function sym(name)
  return getAddressSafe(name)
end

local function setMaster(v)
  local a=sym("projectileSwapEnable")
  if a then writeBytes(a,v and 1 or 0) end
end

local form=createForm(false)
_G.HALO_PROJECTILE_ARMORY_FORM=form
form.Caption="Halo Weapon Lab - Projectile Armory v1.0s"
form.Width=1600
form.Height=960
form.Position="poScreenCenter"
form.AutoSize=false

-- Public-release layout: intentionally generous spacing for high-DPI systems.
-- Long explanatory labels use fixed rectangles + WordWrap so Windows display
-- scaling (including 300%) cannot force them into neighboring controls.
local title=createLabel(form)
title.Caption="Three projectile replacement presets [shared upstream hook; ballistic + energy projectiles]. After a mission change, click Re-resolve &amp; Enable once."
title.Left=30
title.Top=20
title.Width=1520
title.Height=58
title.AutoSize=false
title.WordWrap=true

local countLabel=createLabel(form)
countLabel.Caption="Current armory: not scanned"
countLabel.Left=30
countLabel.Top=88
countLabel.Width=480
countLabel.Height=36
countLabel.AutoSize=false

local legend=createLabel(form)
legend.Caption="Source and target lists include every projectile loaded in the current mission. The shared upstream selection hook covers both ballistic paths (Shotgun, SMG) and energy-projectile paths."
legend.Left=30
legend.Top=130
legend.Width=1520
legend.Height=58
legend.AutoSize=false
legend.WordWrap=true
legend.Font.Color=0x0000A0A0

local hdr1=createLabel(form); hdr1.Caption="Enable"; hdr1.Left=30; hdr1.Top=205
local hdr2=createLabel(form); hdr2.Caption="Source Projectile"; hdr2.Left=130; hdr2.Top=205
local hdr3=createLabel(form); hdr3.Caption="Replace With"; hdr3.Left=800; hdr3.Top=205

local rows={}
local refreshing=false
local armory={}

for i=1,3 do
  local y=240+(i-1)*92
  local r={}
  r.enabled=createCheckBox(form)
  r.enabled.Left=34; r.enabled.Top=y+7; r.enabled.Checked=true
  r.number=createLabel(form)
  r.number.Caption=tostring(i)..". "
  r.number.Left=88; r.number.Top=y+8
  r.source=createComboBox(form)
  r.source.Left=130; r.source.Top=y; r.source.Width=620; r.source.DropDownCount=18
  r.arrow=createLabel(form)
  r.arrow.Caption="→"
  r.arrow.Left=765; r.arrow.Top=y+8
  r.target=createComboBox(form)
  r.target.Left=800; r.target.Top=y; r.target.Width=720; r.target.DropDownCount=20
  r.note=createLabel(form)
  r.note.Caption=""
  r.note.Left=130; r.note.Top=y+43; r.note.Width=1390; r.note.Height=42
  r.note.AutoSize=false
  r.note.WordWrap=true
  r.preferredSource=DEFAULT_SOURCE[i]
  r.preferredTarget=ROCKET_PATH

  r.sourcePaths={}
  r.targetPaths={}

  rows[i]=r
end

local function itemIndexByPath(paths,path)
  if path==nil then return -1 end
  for j,p in ipairs(paths or {}) do
    if p==path then return j-1 end
  end
  return -1
end

local function findProjectile(path)
  for _,p in ipairs(armory) do if p.path==path then return p end end
  return nil
end

local function comboProjectile(combo,paths)
  local idx=combo.ItemIndex
  if idx==nil or idx&lt;0 then return nil end
  local path=paths and paths[idx+1] or nil
  if path==nil then return nil end
  return findProjectile(path)
end

local function updateRowVisual(r)
  local src=comboProjectile(r.source,r.sourcePaths)
  local dst=comboProjectile(r.target,r.targetPaths)
  if src and src.special then
    r.source.Font.Color=0x0000A0A0
  else
    r.source.Font.Color=0x00000000
  end
  if dst and dst.special then
    r.target.Font.Color=0x0000A0A0
  else
    r.target.Font.Color=0x00000000
  end
  local notes={}
  if src==nil then notes[#notes+1]="Source projectile is not loaded in this mission; this preset was disabled automatically" end
  if src and src.special then notes[#notes+1]="🟨 Vehicle/NPC source projectiles may not match in P1-only mode because the emitter handle can differ; try All if needed" end

  if dst and string.lower(dst.path or ""):find("objects\\characters\\hunter\\hunter_fuel_rod",1,true)==1 then
    notes[#notes+1]="Note: this is the Hunter arm-cannon projectile, not the player Fuel Rod weapon; trail/VFX may differ"
  end
  if dst and r.preferredTarget~=dst.path then notes[#notes+1]="Preferred target is not loaded in this mission -&gt; Rocket Launcher fallback selected" end
  r.note.Caption=table.concat(notes,"    ")
end

for _,r in ipairs(rows) do
  local row=r
  row.source.OnChange=function(sender)
    if refreshing then return end
    local p=comboProjectile(row.source,row.sourcePaths)
    if p then row.preferredSource=p.path end
    updateRowVisual(row)
  end
  row.target.OnChange=function(sender)
    if refreshing then return end
    local p=comboProjectile(row.target,row.targetPaths)
    if p then row.preferredTarget=p.path end
    updateRowVisual(row)
  end
end

local function refillCombos(preservePreferred)
  refreshing=true
  for _,r in ipairs(rows) do
    r.source.Items.clear()
    r.target.Items.clear()
    r.sourcePaths={}
    r.targetPaths={}
  end

  for _,p in ipairs(armory) do
    local mark=p.special and "🟨 " or ""
    local suffix=""
    local display=mark..p.name..suffix

    for _,r in ipairs(rows) do
      r.source.Items.add(display)
      r.sourcePaths[#r.sourcePaths+1]=p.path
      r.target.Items.add(display)
      r.targetPaths[#r.targetPaths+1]=p.path
    end
  end

  local rocket=findProjectile(ROCKET_PATH)
  for i,r in ipairs(rows) do
    local si=itemIndexByPath(r.sourcePaths,r.preferredSource)
    r.source.ItemIndex=si

    local ti=itemIndexByPath(r.targetPaths,r.preferredTarget)
    if ti&lt;0 and rocket~=nil then
      ti=itemIndexByPath(r.targetPaths,ROCKET_PATH)
    end
    if ti&lt;0 and r.target.Items.Count&gt;0 then ti=0 end
    r.target.ItemIndex=ti

    if si&lt;0 then r.enabled.Checked=false end
    updateRowVisual(r)
  end
  refreshing=false
end

local function rescan()
  setMaster(false)
  countLabel.Caption="Current armory: scanning... (replacement safely disabled)"
  form.repaint()
  local list,err=scanProjectiles()
  if list==nil then
    countLabel.Caption="Scan failed: "..tostring(err)
    return false
  end
  armory=list
  refillCombos(true)
  countLabel.Caption=string.format("Current armory: %d projectiles",#armory)
  return true
end

local function reportUntranslated()
  if armory==nil or #armory==0 then
    if not rescan() then return end
  end

  local unknown={}
  for _,p in ipairs(armory) do
    if not p.translated then
      unknown[#unknown+1]=p
    end
  end

  if #unknown==0 then
    translationStatus.Caption="Current mission: every loaded projectile has a friendly English name"
    showMessage("Name-map check complete: all "..tostring(#armory).." loaded projectiles have friendly English names.")
    return
  end

  local userProfile=os.getenv("USERPROFILE")
  local outputPath
  if userProfile then
    outputPath=userProfile.."\\Desktop\\Halo_Projectile_Untranslated.txt"
  else
    outputPath=getCheatEngineDir().."Halo_Projectile_Untranslated.txt"
  end

  local f=io.open(outputPath,"w")
  if not f then
    showMessage("Found "..tostring(#unknown).." unmapped projectiles, but the report file could not be created.")
    return
  end

  f:write("Halo Projectile Unmapped Name Report\n")
  f:write("============================================================\n")
  f:write("Total projectiles in current armory: "..tostring(#armory).."\n")
  f:write("Unmapped count: "..tostring(#unknown).."\n")
  f:write("============================================================\n\n")

  for i,p in ipairs(unknown) do
    f:write(string.format("%02d  Datum=%08X\n",i,p.datum))
    f:write("    "..p.path.."\n\n")
  end
  f:close()

  translationStatus.Caption=string.format("Found %d unmapped projectile names; report opened",#unknown)
  pcall(function() shellExecute(outputPath) end)
end

local function apply(enableAfter)
  if not rescan() then return end

  local srcAddr={sym("projectileSourceTag1"),sym("projectileSourceTag2"),sym("projectileSourceTag3")}
  local dstAddr={sym("projectileTargetTag1"),sym("projectileTargetTag2"),sym("projectileTargetTag3")}
  local enaAddr={sym("projectileSlot1Enable"),sym("projectileSlot2Enable"),sym("projectileSlot3Enable")}
  for i,r in ipairs(rows) do
    local src=comboProjectile(r.source,r.sourcePaths)
    local dst=comboProjectile(r.target,r.targetPaths)
    local ok=r.enabled.Checked and src~=nil and dst~=nil
    if srcAddr[i] then writeInteger(srcAddr[i],src and src.datum or 0xFFFFFFFF) end
    if dstAddr[i] then writeInteger(dstAddr[i],dst and dst.datum or 0xFFFFFFFF) end
    if enaAddr[i] then writeBytes(enaAddr[i],ok and 1 or 0) end
  end

  local scopeAddr=sym("projectileScopeMode")
  if scopeAddr then writeBytes(scopeAddr,scopeCombo.ItemIndex==1 and 1 or 0) end
  setMaster(enableAfter)

  if enableAfter then
    countLabel.Caption=countLabel.Caption.."    ● Enabled"
  else
    countLabel.Caption=countLabel.Caption.."    ○ Written but disabled"
  end
end

local scopeText=createLabel(form)
scopeText.Caption="Scope:"
scopeText.Left=30; scopeText.Top=535
scopeCombo=createComboBox(form)
scopeCombo.Left=130; scopeCombo.Top=525; scopeCombo.Width=360
scopeCombo.Items.add("P1 only (recommended)")
scopeCombo.Items.add("All")
scopeCombo.ItemIndex=0

local actionText=createLabel(form)
actionText.Caption="Actions:"
actionText.Left=30; actionText.Top=610

local refreshBtn=createButton(form)
refreshBtn.Caption="Refresh Current-Mission Armory"
refreshBtn.Left=130; refreshBtn.Top=590; refreshBtn.Width=330; refreshBtn.Height=52
refreshBtn.OnClick=function() rescan() end

local applyBtn=createButton(form)
applyBtn.Caption="Re-resolve &amp; Enable"
applyBtn.Left=480; applyBtn.Top=590; applyBtn.Width=290; applyBtn.Height=52
applyBtn.OnClick=function() apply(true) end

local writeBtn=createButton(form)
writeBtn.Caption="Write Only (Do Not Enable)"
writeBtn.Left=790; writeBtn.Top=590; writeBtn.Width=330; writeBtn.Height=52
writeBtn.OnClick=function() apply(false) end

local offBtn=createButton(form)
offBtn.Caption="Disable All"
offBtn.Left=1140; offBtn.Top=590; offBtn.Width=220; offBtn.Height=52
offBtn.OnClick=function()
  setMaster(false)
  local a1=sym("projectileSlot1Enable"); if a1 then writeBytes(a1,0) end
  local a2=sym("projectileSlot2Enable"); if a2 then writeBytes(a2,0) end
  local a3=sym("projectileSlot3Enable"); if a3 then writeBytes(a3,0) end
  countLabel.Caption=countLabel.Caption:gsub("    ● Enabled",""):gsub("    ○ Written but disabled","").."    ○ Disabled"
end

local translationText=createLabel(form)
translationText.Caption="Name map:"
translationText.Left=30; translationText.Top=680

local translationBtn=createButton(form)
translationBtn.Caption="Scan Unmapped Projectile Names"
translationBtn.Left=130; translationBtn.Top=660; translationBtn.Width=360; translationBtn.Height=52
translationBtn.OnClick=function() reportUntranslated() end

translationStatus=createLabel(form)
translationStatus.Caption="After entering a different mission, scan here for newly loaded projectile names."
translationStatus.Left=520; translationStatus.Top=676; translationStatus.Width=1000; translationStatus.Height=46
translationStatus.AutoSize=false
translationStatus.WordWrap=true

local foot=createLabel(form)
foot.Caption="Defaults: (1) Plasma Pistol  (2) Plasma Rifle  (3) Needler; all three targets are Rocket Launcher. If a preferred target is unavailable, Rocket Launcher is used as fallback."
foot.Left=30; foot.Top=745; foot.Width=1520; foot.Height=48
foot.AutoSize=false; foot.WordWrap=true

local foot2=createLabel(form)
foot2.Caption="Tip: Plasma Pistol (Normal) -&gt; Plasma Pistol (Charged) makes a normal trigger pull fire the charged projectile immediately."
foot2.Left=30; foot2.Top=795; foot2.Width=1520; foot2.Height=44
foot2.AutoSize=false; foot2.WordWrap=true
foot2.Font.Color=0x00008000

local foot3=createLabel(form)
foot3.Caption="Note: 🟨 marks special vehicle/NPC projectiles. CE standard ComboBox controls cannot reliably color individual dropdown items."
foot3.Left=30; foot3.Top=840; foot3.Width=1520; foot3.Height=44
foot3.AutoSize=false; foot3.WordWrap=true
foot3.Font.Color=0x0000A0A0

local foot4=createLabel(form)
foot4.Caption="Projectile notes: Plasma Launcher Grenade replacement currently lacks its native green trail; Beam Rifle projectile replacement is verified. Scorpion and Wraith weapon names use exact full-path mapping."
foot4.Left=30; foot4.Top=885; foot4.Width=1520; foot4.Height=50
foot4.AutoSize=false; foot4.WordWrap=true
foot4.Font.Color=0x000000A0

form.OnClose=function(sender)
  setMaster(false)
  _G.HALO_PROJECTILE_ARMORY_FORM=nil
  return caFree
end

rescan()
form.show()
{$asm}

[DISABLE]
{$lua}
if syntaxcheck then return end
local a=getAddressSafe("projectileSwapEnable")
if a then writeBytes(a,0) end
if _G.HALO_PROJECTILE_ARMORY_FORM ~= nil then
  pcall(function() _G.HALO_PROJECTILE_ARMORY_FORM.close() end)
  _G.HALO_PROJECTILE_ARMORY_FORM=nil
end
{$asm}</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>99010</ID>
              <Description>"Advanced / runtime values (normally keep collapsed)"</Description>
              <Options moHideChildren="1" />
              <GroupHeader>1</GroupHeader>
              <CheatEntries>
                <CheatEntry>
                  <ID>99011</ID>
                  <Description>"Master enable"</Description>
                  <ShowAsHex>0</ShowAsHex>
                  <VariableType>Byte</VariableType>
                  <Address>projectileSwapEnable</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>99012</ID>
                  <Description>"Scope 0=P1  1=All"</Description>
                  <ShowAsHex>0</ShowAsHex>
                  <VariableType>Byte</VariableType>
                  <Address>projectileScopeMode</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>99013</ID>
                  <Description>"Slot 1 enable"</Description>
                  <ShowAsHex>0</ShowAsHex>
                  <VariableType>Byte</VariableType>
                  <Address>projectileSlot1Enable</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>99014</ID>
                  <Description>"Slot 1 source tag"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>4 Bytes</VariableType>
                  <Address>projectileSourceTag1</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>99015</ID>
                  <Description>"Slot 1 target tag"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>4 Bytes</VariableType>
                  <Address>projectileTargetTag1</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>99016</ID>
                  <Description>"Slot 2 enable"</Description>
                  <ShowAsHex>0</ShowAsHex>
                  <VariableType>Byte</VariableType>
                  <Address>projectileSlot2Enable</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>99017</ID>
                  <Description>"Slot 2 source tag"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>4 Bytes</VariableType>
                  <Address>projectileSourceTag2</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>99018</ID>
                  <Description>"Slot 2 target tag"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>4 Bytes</VariableType>
                  <Address>projectileTargetTag2</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>99019</ID>
                  <Description>"Slot 3 enable"</Description>
                  <ShowAsHex>0</ShowAsHex>
                  <VariableType>Byte</VariableType>
                  <Address>projectileSlot3Enable</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>99020</ID>
                  <Description>"Slot 3 source tag"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>4 Bytes</VariableType>
                  <Address>projectileSourceTag3</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>99021</ID>
                  <Description>"Slot 3 target tag"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>4 Bytes</VariableType>
                  <Address>projectileTargetTag3</Address>
                </CheatEntry>
              </CheatEntries>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>99400</ID>
          <Description>"Vehicle Lab"</Description>
          <Options />
          <Color>00A5FF</Color>
          <GroupHeader>1</GroupHeader>
          <CheatEntries>
            <CheatEntry>
              <ID>99401</ID>
              <Description>"Covenant Vehicle Infinite Boost"</Description>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]

// ============================================================
//
//
//   vfnmadd231ss xmm8,xmm0,[rax+08]
//
//
// ============================================================

aobscanmodule(InfiniteVehicleBoostAOB,HaloSimulation_tag_release.dll,C5 AA 59 85 7C 04 00 00 C4 62 79 BD 40 08 C5 BA 5F C6 C5 7A 5D C7)
assert(InfiniteVehicleBoostAOB+08,C4 62 79 BD 40 08)

registersymbol(InfiniteVehicleBoostAOB)

InfiniteVehicleBoostAOB+08:
  db 90 90 90 90 90 90

[DISABLE]

InfiniteVehicleBoostAOB+08:
  db C4 62 79 BD 40 08

unregistersymbol(InfiniteVehicleBoostAOB)</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>99402</ID>
              <Description>"Boost Power Parameter (Global / AI Test)"</Description>
              <Options moHideChildren="1" moDeactivateChildrenAsWell="1" />
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]

// ============================================================
//
//   test byte ptr [vehicleBoostObject+9D8],20
//   ...
//   vfmsub231ss xmm0,xmm0,[rbx+470]
//   vaddss xmm12,xmm0,xmm10      // xmm10 = 1.0
//
//
//
// ============================================================

aobscanmodule(VehicleBoostPowerAOB,HaloSimulation_tag_release.dll,41 8B C9 E8 ?? ?? ?? ?? C4 E2 79 BB 83 70 04 00 00 C5 FA 10 4C 24 40 C5 FA 10 54 24 44 C4 41 7A 58 E2)
assert(VehicleBoostPowerAOB+08,C4 E2 79 BB 83 70 04 00 00)

alloc(VehicleBoostPowerNewmem,$1000,VehicleBoostPowerAOB+08)
label(VehicleBoostPowerReturn)
label(VehicleBoostPowerTarget)

registersymbol(VehicleBoostPowerAOB)
registersymbol(VehicleBoostPowerTarget)

VehicleBoostPowerNewmem:
  vmovss xmm12,[VehicleBoostPowerTarget]
  vsubss xmm12,xmm12,xmm10
  vmulss xmm0,xmm0,xmm12
  jmp VehicleBoostPowerReturn

align 4
VehicleBoostPowerTarget:
  dd (float)6.0

VehicleBoostPowerAOB+08:
  jmp VehicleBoostPowerNewmem
  nop 4
VehicleBoostPowerReturn:

[DISABLE]

VehicleBoostPowerAOB+08:
  db C4 E2 79 BB 83 70 04 00 00

unregistersymbol(VehicleBoostPowerTarget)
unregistersymbol(VehicleBoostPowerAOB)
dealloc(VehicleBoostPowerNewmem)</AssemblerScript>
              <CheatEntries>
                <CheatEntry>
                  <ID>99403</ID>
                  <Description>"Target Boost Power (Wraith stock = 3; try 6; 10 can self-destruct on impact)"</Description>
                  <ShowAsSigned>0</ShowAsSigned>
                  <VariableType>Float</VariableType>
                  <Address>VehicleBoostPowerTarget</Address>
                </CheatEntry>
              </CheatEntries>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>99600</ID>
          <Description>"Explosion Lab"</Description>
          <Options moHideChildren="1" moDeactivateChildrenAsWell="1" />
          <Color>FFB266</Color>
          <GroupHeader>1</GroupHeader>
          <CheatEntries>
            <CheatEntry>
              <ID>8016</ID>
              <Description>"Explosion Radius Multiplier (Boom Rule)"</Description>
              <Options moHideChildren="1" moDeactivateChildrenAsWell="1" />
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
aobscanmodule(boomSkullStateAOB,HaloSimulation_tag_release.dll,4A 8B 04 C0 48 8B 04 08 48 8B 80 E0 EB 01 00 48 0F A3 D8)
alloc(boomSkullStateOriginal,7,boomSkullStateAOB+8)
registersymbol(boomSkullStateOriginal)
boomSkullStateOriginal:
  readmem(boomSkullStateAOB+8,7)
alloc(boomSkullStateNewmem,$1000,boomSkullStateAOB)
label(boomSkullStateReturn)
label(boomSkullStateBase)
registersymbol(boomSkullStateAOB)
registersymbol(boomSkullStateBase)

boomSkullStateNewmem:
  // Low-level skull-state capture adapted from khuong's original table.
  mov [boomSkullStateBase],rax
  mov rax,[rax+0001EBE0]
  jmp boomSkullStateReturn
align 10
boomSkullStateBase:
  dq 0
boomSkullStateAOB+8:
  jmp boomSkullStateNewmem
  nop 2
boomSkullStateReturn:

// v1.0k: do NOT hook +67A5EC anymore.
// Keep a user-selected Float in CE memory, then repeatedly:
//   1) keep the real Boom skull bit ON after death/checkpoint reloads
//   2) resolve the CURRENT Boom config Meta through the game's Tag system
//   3) write the multiplier directly to Meta+28 (the source field proven by testing)
alloc(_boomRadiusMultiplier,4,HaloSimulation_tag_release.dll)
registersymbol(_boomRadiusMultiplier)
_boomRadiusMultiplier:
  dd (float)2.0
{$lua}
if syntaxcheck then return end

-- Remove any stale timer from an earlier activation.
local old = _G.HaloBoomRadiusState
if old ~= nil and old.timer ~= nil then
  pcall(function() old.timer.destroy() end)
end

local state = {}
_G.HaloBoomRadiusState = state

state.resolveMeta = function()
  local m = getAddressSafe("HaloSimulation_tag_release.dll")
  if m == nil then return nil end

  local globals = readQword(m + 0x10C3550)
  if globals == nil or globals == 0 then return nil end

  local datum = readInteger(globals + 0x150)
  if datum == nil then return nil end
  datum = datum &amp; 0xFFFFFFFF

  local tagSystem = readQword(m + 0x182D1E8)
  if tagSystem == nil or tagSystem == 0 then return nil end

  local entryBase = readQword(tagSystem + 0x50)
  if entryBase == nil or entryBase == 0 then return nil end

  local index = datum &amp; 0xFFFF
  local entry = entryBase + index * 0x30

  -- Reject a stale/invalid datum before following the packed Meta field.
  local header = readInteger(entry + 0x00)
  if header == nil then return nil end
  local salt = header &amp; 0xFFFF
  local rebuilt = ((salt &lt;&lt; 16) | index) &amp; 0xFFFFFFFF
  if rebuilt ~= datum then return nil end

  local packed = readInteger(entry + 0x1C)
  if packed == nil then return nil end
  packed = packed &amp; 0xFFFFFFFF

  local shardIndex = (packed &gt;&gt; 28) &amp; 0xF
  local shardPtr = readQword(m + 0x2C2CCC0 + shardIndex * 8)
  if shardPtr == nil or shardPtr == 0 then return nil end

  return shardPtr + packed * 4
end

state.keepBoomOn = function()
  local p = getAddressSafe("boomSkullStateBase")
  if p == nil then return end
  local base = readQword(p)
  if base == nil or base == 0 then return end
  local a = base + 0x1EBE2
  local v = readBytes(a, 1, true)
  if v ~= nil and (v[1] &amp; 0x10) == 0 then
    writeBytes(a, v[1] | 0x10)
  end
end

state.apply = function()
  state.keepBoomOn()

  local multAddr = getAddressSafe("_boomRadiusMultiplier")
  if multAddr == nil then return end
  local mult = readFloat(multAddr)
  if mult == nil then return end

  local meta = state.resolveMeta()
  if meta == nil or meta == 0 then return end

  local target = meta + 0x28
  local cur = readFloat(target)
  if cur == nil then return end

  if math.abs(cur - mult) &gt; 0.0001 then
    writeFloat(target, mult)
  end
  state.lastMeta = meta
end

local t = createTimer(nil)
t.Enabled = false
t.Interval = 100
t.OnTimer = function()
  pcall(state.apply)
end
state.timer = t
t.Enabled = true

-- First application happens immediately as well as on the timer.
pcall(state.apply)
{$asm}


[DISABLE]
{$lua}
if syntaxcheck then return end

local state = _G.HaloBoomRadiusState
if state ~= nil then
  if state.timer ~= nil then
    pcall(function() state.timer.destroy() end)
    state.timer = nil
  end

  -- Restore the CURRENT Boom source multiplier to the shipped value.
  local meta = nil
  if state.resolveMeta ~= nil then
    local ok, result = pcall(state.resolveMeta)
    if ok then meta = result end
  end
  if (meta == nil or meta == 0) and state.lastMeta ~= nil then
    meta = state.lastMeta
  end
  if meta ~= nil and meta ~= 0 then
    pcall(function() writeFloat(meta + 0x28, 2.0) end)
  end
end

-- This CT item owns the Boom switch, so disabling it clears the Boom bit.
local p = getAddressSafe("boomSkullStateBase")
if p ~= nil then
  local base = readQword(p)
  if base ~= nil and base ~= 0 then
    local a = base + 0x1EBE2
    local v = readBytes(a, 1, true)
    if v ~= nil then
      writeBytes(a, v[1] &amp; ~0x10 &amp; 0xFF)
    end
  end
end

_G.HaloBoomRadiusState = nil
{$asm}
unregistersymbol(_boomRadiusMultiplier)
dealloc(_boomRadiusMultiplier)
boomSkullStateAOB+8:
  readmem(boomSkullStateOriginal,7)
unregistersymbol(boomSkullStateBase)
unregistersymbol(boomSkullStateAOB)
unregistersymbol(boomSkullStateOriginal)
dealloc(boomSkullStateNewmem)
dealloc(boomSkullStateOriginal)</AssemblerScript>
              <CheatEntries>
                <CheatEntry>
                  <ID>99110</ID>
                  <Description>"Explosion Radius Multiplier"</Description>
                  <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">1:1.0x  Stock explosion radius
1.5:1.5x  Mild
2:2.0x  Shipped Boom
2.5:2.5x
3:3.0x
5:5.0x  Extreme
10:10x  Wild
50:50x  Nuclear-scale</DropDownList>
                  <VariableType>Float</VariableType>
                  <Address>_boomRadiusMultiplier</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>99111</ID>
                  <Description>"Writes the current Boom config Meta+28 directly, re-resolves after death/checkpoint reloads, and keeps Boom enabled. 50x produces map-scale blast radii; known behavior: in tight/enclosed spaces, 50x may sometimes deal 0 damage."</Description>
                  <GroupHeader>1</GroupHeader>
                </CheatEntry>
              </CheatEntries>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
  </CheatEntries>
  <UserdefinedSymbols />
  <Comments>================================================================
 Halo Weapon Lab - Public Edition 1.0.2
================================================================
 Game          : Halo Campaign Evolved
 Target        : HaloCampaignEvolved.exe
 Known build   : Meteorite+Rel-i343-Meteorite-2607-CU3 (5.5.4-1112544)
 Mode          : Single-player only
 OCT ID        : huqi

 Purpose
 ----------------------------------------------------------------
 This is a focused weapon-systems laboratory, not a conventional
 all-purpose trainer. Standard god mode / ammo / movement / checkpoint
 / skull toggles from the original table were intentionally removed.

 Public modules
 ----------------------------------------------------------------
 - Instant Plasma Pistol MAX charge
 - Fire-rate scaling, volley count, weapon-type scope and magazine auto-fill
 - Semi-auto / heavy-weapon cooldown unlock
 - Non-invasive energy-weapon runtime monitor
 - Projectile Ballistics editor (range, velocity, shotgun spread/pellet grid)
 - Projectile Armory with three persistent source -&gt; target presets
 - Mission-safe re-resolution of projectile IDs
 - Vehicle Lab (Covenant boost energy + boost power)
 - Explosion Radius editor using the shipped Boom configuration

 Projectile Armory - mission behavior
 ----------------------------------------------------------------
 Every mission loads a different projectile armory. The table remembers
 your three presets by projectile path/name rather than keeping stale
 mission-local runtime IDs.

 Example:
   Prologue: Magnum -&gt; Seraph Quad Bomb Launcher
   Mission 1: if the Seraph Quad Bomb Launcher projectile is not loaded in that
   mission, clicking "Re-resolve &amp; Enable" keeps Magnum as the source but
   automatically falls back to Rocket Launcher as the target.

 This fallback is intentional: a missing preferred TARGET becomes Rocket
 Launcher instead of leaving a stale target ID that could crash the game.
 If a preferred SOURCE itself is not present in the current mission, that
 preset is disabled until the source exists again.

 After entering a new mission, weapons may temporarily return to stock
 behavior. Click "Re-resolve &amp; Enable" once. The table rescans the current
 mission, matches the saved source/target paths to the new runtime IDs,
 and restores the same presets automatically.

 Projectile Armory - scope
 ----------------------------------------------------------------
 P1 only (recommended): replacements are filtered to Player 1's weapon.

 All: the source-projectile rule is applied globally. Friendly and enemy
 NPCs using a matched source weapon can therefore fire the replacement
 projectile as well. Use this mode deliberately, because one preset can
 change the behavior of every matching weapon currently in the mission.

 Explosion Radius notes
 ----------------------------------------------------------------
 Very large values are intentionally experimental. 50x can create
 map-scale blast radii, but in tight/enclosed spaces it may sometimes
 produce 0 damage. Treat 50x as an extreme test value rather than a
 universally reliable combat setting.

 Credits / provenance
 ----------------------------------------------------------------
 huqi (OCT): Halo Weapon Lab public edition, Weapon Lab systems,
 Projectile Armory, projectile/ballistics mappings and experimental
 parameter editors in this release.

 khuong: original Halo Campaign Evolved table. This public edition does
 not redistribute the original table's conventional trainer features.
 Selected low-level logic/code-site references were adapted with credit:
 - P1 filtering logic at the Projectile Armory hook site
 - skull-state capture used by the Explosion Radius editor
 - the energy-weapon code site used by the runtime monitor

 Single-player only.
================================================================</Comments>
</CheatTable>