<?xml version="1.0" encoding="utf-8"?>
<CheatTable CheatEngineTableVersion="45">
  <CheatEntries>
    <CheatEntry>
      <ID>1</ID>
      <Description>"[ATTACH / ALL OFF] Attach to History_Game-Win64-Shipping.exe + disable everything"</Description>
      <Color>00A5FF</Color>
      <VariableType>Auto Assembler Script</VariableType>
      <AssemblerScript>

{ Game   : History_Game-Win64-Shipping.exe  (The End of History / Steam app 2953520)
  Author  : Black_wolf
　CheatVer:20261028-001-015

  Memo: 対応 Steam Build ID は 26248873 2026-10-28
　　　　ゲームエンジン: Unreal Engine 5.7.4 / CL-51494982
　　　　モジュールのビルド日時: 0x1A9AE4D5 ／ SizeOfImage 0x985E000

　　　　ゲームデータは UE リフレクション（クラス名・プロパティ名）で解決するため固定オフセットを使用しない
}

[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.disableAll(memrec) end)
{$asm}
[DISABLE]
</AssemblerScript>
    </CheatEntry>
    <CheatEntry>
      <ID>2</ID>
      <Description>"0. Settings &amp; README"</Description>
      <Options moManualExpandCollapse="1"/>
      <Color>FFFF00</Color>
      <GroupHeader>1</GroupHeader>
      <CheatEntries>
        <CheatEntry>
          <ID>3</ID>
          <Description>"Language / 言語設定"</Description>
          <Options moManualExpandCollapse="1"/>
          <Color>FFFF00</Color>
          <GroupHeader>1</GroupHeader>
          <CheatEntries>
            <CheatEntry>
              <ID>4</ID>
              <Description>"English (default)"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.setLang('en') end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>5</ID>
              <Description>"日本語 / Japanese"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.setLang('ja') end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>6</ID>
          <Description>"README"</Description>
          <Options moManualExpandCollapse="1"/>
          <Color>FFFF00</Color>
          <GroupHeader>1</GroupHeader>
          <CheatEntries>
            <CheatEntry>
              <ID>7</ID>
              <Description>"1) Start the game, LOAD A SAVE, open this table (allow its Lua script), then run [ATTACH / ALL OFF] at the top."</Description>
              <GroupHeader>1</GroupHeader>
            </CheatEntry>
            <CheatEntry>
              <ID>8</ID>
              <Description>"2) Updated for Steam Build ID 26248873 (installed 2026-10-28). Game data is found by Unreal reflection (class / property names). Re-run ATTACH after a game update."</Description>
              <GroupHeader>1</GroupHeader>
            </CheatEntry>
            <CheatEntry>
              <ID>9</ID>
              <Description>"3) Orange = script (check to run / toggle), cyan = expandable group, no color = value (double-click to edit)."</Description>
              <GroupHeader>1</GroupHeader>
            </CheatEntry>
            <CheatEntry>
              <ID>10</ID>
              <Description>"4) Messages appear in the Lua Engine window (0. &gt; Diagnostics &gt; Lua Engine)."</Description>
              <GroupHeader>1</GroupHeader>
            </CheatEntry>
            <CheatEntry>
              <ID>11</ID>
              <Description>"5) Save your game before using editor / one-shot actions."</Description>
              <GroupHeader>1</GroupHeader>
            </CheatEntry>
            <CheatEntry>
              <ID>19</ID>
              <Description>"6) After loading another save, uncheck and re-check lists so that they point to the new data."</Description>
              <GroupHeader>1</GroupHeader>
            </CheatEntry>
            <CheatEntry>
              <ID>20</ID>
              <Description>"7) Wage, food flag, speed etc. are recalculated by the game; ON/OFF features rewrite them every 0.5 s."</Description>
              <GroupHeader>1</GroupHeader>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>12</ID>
          <Description>"Diagnostics (only when the table does not work)"</Description>
          <Options moManualExpandCollapse="1"/>
          <Color>FFFF00</Color>
          <GroupHeader>1</GroupHeader>
          <CheatEntries>
            <CheatEntry>
              <ID>13</ID>
              <Description>"Lua Engine (check = open / uncheck = close)"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.openLuaEngine()
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.closeLuaEngine() end
{$asm}
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>14</ID>
              <Description>"AOB scan self-test (CE is busy for a while; prints to Lua Engine)"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.scanSelfTest() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>15</ID>
              <Description>"Environment / connection info (CE, process, module, build)"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.envReport() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>16</ID>
              <Description>"Status: unresolved code / active hooks / active features"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.statusReport() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>17</ID>
              <Description>"UE reflection check (layouts, class instances, player)"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.ueCheck() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
    <CheatEntry>
      <ID>100</ID>
      <Description>"01. Player"</Description>
      <Options moManualExpandCollapse="1"/>
      <Color>FFFF00</Color>
      <GroupHeader>1</GroupHeader>
      <CheatEntries>
        <CheatEntry>
          <ID>101</ID>
          <Description>"Invincible (HP / health / sanity / stamina kept full, also in realtime battle)"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.feature('invincible', true)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.feature('invincible', false) end
{$asm}
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>102</ID>
          <Description>"Movement speed max (world map and realtime battle, multiplier)"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.feature('pSpeed', true)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.feature('pSpeed', false) end
{$asm}
</AssemblerScript>
          <CheatEntries>
            <CheatEntry>
              <ID>103</ID>
              <Description>"Speed multiplier"</Description>
              <ShowAsSigned>0</ShowAsSigned>
              <VariableType>Float</VariableType>
              <Address>gtVars+20</Address>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>107</ID>
          <Description>"Influence max (kept at least at the value)"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.feature('influence', true)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.feature('influence', false) end
{$asm}
</AssemblerScript>
          <CheatEntries>
            <CheatEntry>
              <ID>108</ID>
              <Description>"Influence value"</Description>
              <ShowAsSigned>0</ShowAsSigned>
              <VariableType>Double</VariableType>
              <Address>gtVars+28</Address>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>109</ID>
          <Description>"Money: kept at least at the value"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.feature('money', true)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.feature('money', false) end
{$asm}
</AssemblerScript>
          <CheatEntries>
            <CheatEntry>
              <ID>110</ID>
              <Description>"Money value"</Description>
              <ShowAsSigned>0</ShowAsSigned>
              <VariableType>Double</VariableType>
              <Address>gtVars+30</Address>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>111</ID>
          <Description>"Never age (age frozen)"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.feature('age', true)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.feature('age', false) end
{$asm}
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>104</ID>
          <Description>"Learn all battle skills"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.learnAllSkills() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>105</ID>
          <Description>"All battle skills at max level"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.maxAllSkills() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>113</ID>
          <Description>"All technologies acquired (your country)"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.allTech() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>115</ID>
          <Description>"Extra: six abilities EXP max (Leadership etc.)"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.maxAbilities() end)
{$asm}
[DISABLE]
</AssemblerScript>
          <CheatEntries>
            <CheatEntry>
              <ID>106</ID>
              <Description>"EXP value (0 = game maximum)"</Description>
              <ShowAsSigned>1</ShowAsSigned>
              <VariableType>4 Bytes</VariableType>
              <Address>gtVars+24</Address>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>112</ID>
          <Description>"Player editor"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.userRun(function() GT.G.openEditor(memrec, GT.G.listPlayer) end)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.clearChildren(memrec) end
{$asm}
</AssemblerScript>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
    <CheatEntry>
      <ID>200</ID>
      <Description>"02. Army"</Description>
      <Options moManualExpandCollapse="1"/>
      <Color>FFFF00</Color>
      <GroupHeader>1</GroupHeader>
      <CheatEntries>
        <CheatEntry>
          <ID>208</ID>
          <Description>"Army invincible (soldiers HP / stamina / morale max, food sufficient and never decreases)"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.feature('armyGod', true)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.feature('armyGod', false) end
{$asm}
</AssemblerScript>
          <CheatEntries>
            <CheatEntry>
              <ID>205</ID>
              <Description>"Morale value"</Description>
              <ShowAsSigned>1</ShowAsSigned>
              <VariableType>4 Bytes</VariableType>
              <Address>gtVars+38</Address>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>201</ID>
          <Description>"Army cost 0 (total wage kept 0)"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.feature('armyCost0', true)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.feature('armyCost0', false) end
{$asm}
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>206</ID>
          <Description>"Army movement speed (mobility multiplier)"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.feature('armySpeed', true)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.feature('armySpeed', false) end
{$asm}
</AssemblerScript>
          <CheatEntries>
            <CheatEntry>
              <ID>207</ID>
              <Description>"Mobility multiplier"</Description>
              <ShowAsSigned>0</ShowAsSigned>
              <VariableType>Float</VariableType>
              <Address>gtVars+3C</Address>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>209</ID>
          <Description>"Add soldiers to your army (type -&gt; number)..."</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.addSoldiers() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>211</ID>
          <Description>"Change soldier type (several types at once)..."</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.changeSoldierType() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>212</ID>
          <Description>"All soldiers: set level"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.soldierLevelMax() end)
{$asm}
[DISABLE]
</AssemblerScript>
          <CheatEntries>
            <CheatEntry>
              <ID>213</ID>
              <Description>"Soldier level"</Description>
              <ShowAsSigned>1</ShowAsSigned>
              <VariableType>4 Bytes</VariableType>
              <Address>gtVars+44</Address>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>214</ID>
          <Description>"Army editor"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.userRun(function() GT.G.openEditor(memrec, GT.G.listArmy) end)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.clearChildren(memrec) end
{$asm}
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>215</ID>
          <Description>"Soldier editor"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.userRun(function() GT.G.openEditor(memrec, GT.G.listSoldiers) end)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.clearChildren(memrec) end
{$asm}
</AssemblerScript>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
    <CheatEntry>
      <ID>300</ID>
      <Description>"03. Cities"</Description>
      <Options moManualExpandCollapse="1"/>
      <Color>FFFF00</Color>
      <GroupHeader>1</GroupHeader>
      <CheatEntries>
        <CheatEntry>
          <ID>320</ID>
          <Description>"Selected city (select, then edit the values below)"</Description>
          <Options moManualExpandCollapse="1"/>
          <Color>FFFF00</Color>
          <GroupHeader>1</GroupHeader>
          <CheatEntries>
            <CheatEntry>
              <ID>321</ID>
              <Description>"Select: city shown on screen (open the city screen first)"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selSpotShown() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>322</ID>
              <Description>"Select: city you are staying in"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selSpotStay() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>323</ID>
              <Description>"Select: search by name..."</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selSpotSearch() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>324</ID>
              <Description>"Selected city: values by category (check to show; follows the selection)"</Description>
              <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.userRun(function() GT.G.editSpot(memrec) end)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.clearChildren(memrec) end
{$asm}
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>325</ID>
              <Description>"Selected city: seize"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selSeize() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>326</ID>
              <Description>"Selected city: vacate (dismiss the lord)"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selVacate() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>327</ID>
              <Description>"Selected city: normalize (uses the wall value of the item below)"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selNormalize() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>328</ID>
              <Description>"Selected city: all facilities max"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selFacilitiesMax() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>329</ID>
              <Description>"Selected city: impression of you..."</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selSpotImpression() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>301</ID>
          <Description>"Seize cities (put them under your rule; several allowed)..."</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.seizeSpot() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>312</ID>
          <Description>"Vacate cities (dismiss the current lord; several allowed)..."</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.vacateSpots() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>302</ID>
          <Description>"Normalize cities (devastation 0, safety max, walls max; choose cities)"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.normalizeOwnSpots() end)
{$asm}
[DISABLE]
</AssemblerScript>
          <CheatEntries>
            <CheatEntry>
              <ID>303</ID>
              <Description>"Wall value (minimum; wall level x multiplier is used when larger)"</Description>
              <ShowAsSigned>0</ShowAsSigned>
              <VariableType>Double</VariableType>
              <Address>gtVars+48</Address>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>304</ID>
          <Description>"Build all facilities (own cities)"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.buildAll() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>305</ID>
          <Description>"All facilities max level (own cities)"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.facilitiesMax() end)
{$asm}
[DISABLE]
</AssemblerScript>
          <CheatEntries>
            <CheatEntry>
              <ID>306</ID>
              <Description>"Level override (0 = MaxLevel of each facility)"</Description>
              <ShowAsSigned>1</ShowAsSigned>
              <VariableType>4 Bytes</VariableType>
              <Address>gtVars+50</Address>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>307</ID>
          <Description>"Development max (commerce / agriculture / loyalty 100 / safety 100 / war devastation 0 / food surplus / manpower; choose cities)"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.developAll() end)
{$asm}
[DISABLE]
</AssemblerScript>
          <CheatEntries>
            <CheatEntry>
              <ID>308</ID>
              <Description>"Commerce"</Description>
              <ShowAsSigned>0</ShowAsSigned>
              <VariableType>Double</VariableType>
              <Address>gtVars+58</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>309</ID>
              <Description>"Agriculture"</Description>
              <ShowAsSigned>0</ShowAsSigned>
              <VariableType>Double</VariableType>
              <Address>gtVars+60</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>314</ID>
              <Description>"Food surplus"</Description>
              <ShowAsSigned>0</ShowAsSigned>
              <VariableType>Double</VariableType>
              <Address>gtVars+68</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>315</ID>
              <Description>"Manpower"</Description>
              <ShowAsSigned>0</ShowAsSigned>
              <VariableType>Double</VariableType>
              <Address>gtVars+70</Address>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>313</ID>
          <Description>"Cities' impression of you (several cities, value 0-100)..."</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.spotImpression() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>310</ID>
          <Description>"Keep own cities normalized (uses the wall value above)"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.feature('cityKeep', true)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.feature('cityKeep', false) end
{$asm}
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>311</ID>
          <Description>"City editor"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.userRun(function() GT.G.openCityEditor(memrec) end)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.clearChildren(memrec) end
{$asm}
</AssemblerScript>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
    <CheatEntry>
      <ID>400</ID>
      <Description>"04. Items"</Description>
      <Options moManualExpandCollapse="1"/>
      <Color>FFFF00</Color>
      <GroupHeader>1</GroupHeader>
      <CheatEntries>
        <CheatEntry>
          <ID>402</ID>
          <Description>"Add items: quantities"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.userRun(function() GT.G.openItemAdd(memrec) end)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.G.closeItemAdd(memrec) end
{$asm}
</AssemblerScript>
          <Options moHideChildren="1"/>
        </CheatEntry>
        <CheatEntry>
          <ID>403</ID>
          <Description>"Add trade goods / food (several allowed, with quantity)..."</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.addGoods() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>401</ID>
          <Description>"Item quantity editor"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.userRun(function() GT.G.openEditor(memrec, GT.G.listInventory) end)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.clearChildren(memrec) end
{$asm}
</AssemblerScript>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
    <CheatEntry>
      <ID>500</ID>
      <Description>"05. Persons"</Description>
      <Options moManualExpandCollapse="1"/>
      <Color>FFFF00</Color>
      <GroupHeader>1</GroupHeader>
      <CheatEntries>
        <CheatEntry>
          <ID>520</ID>
          <Description>"Selected person (select, then edit the values below)"</Description>
          <Options moManualExpandCollapse="1"/>
          <Color>FFFF00</Color>
          <GroupHeader>1</GroupHeader>
          <CheatEntries>
            <CheatEntry>
              <ID>521</ID>
              <Description>"Select: person shown on screen (open the person screen first)"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selUnitShown() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>522</ID>
              <Description>"Select: search by name..."</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selUnitSearch() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>523</ID>
              <Description>"Select: player"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selUnitPlayer() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>524</ID>
              <Description>"Selected person: values by category (check to show; follows the selection)"</Description>
              <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.userRun(function() GT.G.editUnit(memrec) end)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.clearChildren(memrec) end
{$asm}
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>525</ID>
              <Description>"Selected person: join your faction"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selRecruit() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>526</ID>
              <Description>"Selected person: marry"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selMarry() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>527</ID>
              <Description>"Selected person: impression of you max"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selImpression() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>528</ID>
              <Description>"Selected person: reveal details"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selReveal() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>529</ID>
              <Description>"Selected person: six abilities EXP max (value of item 1. &gt; six abilities)"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.selAbilities() end)
{$asm}
[DISABLE]
</AssemblerScript>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>501</ID>
          <Description>"Force persons to join your faction (several allowed)..."</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.recruit() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>502</ID>
          <Description>"Marry a person..."</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.marry() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>504</ID>
          <Description>"Chosen persons: impression of you max (several allowed)..."</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.oneImpression() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>507</ID>
          <Description>"Reveal details of chosen persons (abilities / characteristics; several allowed)..."</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.revealSome() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>503</ID>
          <Description>"All vassals: impression of you max"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.vassalsImpression() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>506</ID>
          <Description>"Reveal all persons' hidden parameters"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.revealAll() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>505</ID>
          <Description>"Person parameters (check, then pick persons)"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.userRun(function() GT.G.listPerson(memrec) end)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.clearChildren(memrec) end
{$asm}
</AssemblerScript>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
    <CheatEntry>
      <ID>700</ID>
      <Description>"06. Realtime battle"</Description>
      <Options moManualExpandCollapse="1"/>
      <Color>FFFF00</Color>
      <GroupHeader>1</GroupHeader>
      <CheatEntries>
        <CheatEntry>
          <ID>701</ID>
          <Description>"Weaken enemies (enemy HP 1 or less; siege of an enemy spot: walls 1, residents resistance 1, stockpiled food 1)"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.feature('enemyOneHit', true)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.feature('enemyOneHit', false) end
{$asm}
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>702</ID>
          <Description>"Battle units: HP of you / your soldiers / enemies (check to list, during a battle)"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.userRun(function() GT.G.listBattle(memrec) end)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.clearChildren(memrec) end
{$asm}
</AssemblerScript>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
    <CheatEntry>
      <ID>8000</ID>
      <Description>"07. Lists"</Description>
      <Options moManualExpandCollapse="1"/>
      <Color>FFFF00</Color>
      <GroupHeader>1</GroupHeader>
      <CheatEntries>
        <CheatEntry>
          <ID>8001</ID>
          <Description>"Cities"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec,function() GT.G.openWorldList('spot') end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>8002</ID>
          <Description>"Persons"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec,function() GT.G.openWorldList('unit') end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
    <CheatEntry>
      <ID>600</ID>
      <Description>"08. Other"</Description>
      <Options moManualExpandCollapse="1"/>
      <Color>FFFF00</Color>
      <GroupHeader>1</GroupHeader>
      <CheatEntries>
        <CheatEntry>
          <ID>614</ID>
          <Description>"Continue wars (war fatigue kept 0: your country and its enemies; no forced peace at 100)"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.feature('warFatigue0', true)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.feature('warFatigue0', false) end
{$asm}
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>601</ID>
          <Description>"World collapse gauge 0 (division / hatred kept 0)"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.feature('collapse0', true)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.feature('collapse0', false) end
{$asm}
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>602</ID>
          <Description>"Date: set / freeze (values are the live in-game date; edit to change)"</Description>
          <Options moManualExpandCollapse="1"/>
          <Color>FFFF00</Color>
          <GroupHeader>1</GroupHeader>
          <CheatEntries>
            <CheatEntry>
              <ID>604</ID>
              <Description>"Year"</Description>
              <ShowAsSigned>1</ShowAsSigned>
              <VariableType>4 Bytes</VariableType>
              <Address>gtDate_TimeYear</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>605</ID>
              <Description>"Month"</Description>
              <ShowAsSigned>1</ShowAsSigned>
              <VariableType>4 Bytes</VariableType>
              <Address>gtDate_TimeMonth</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>606</ID>
              <Description>"Day"</Description>
              <ShowAsSigned>1</ShowAsSigned>
              <VariableType>4 Bytes</VariableType>
              <Address>gtDate_TimeDay</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>607</ID>
              <Description>"Hour"</Description>
              <ShowAsSigned>1</ShowAsSigned>
              <VariableType>4 Bytes</VariableType>
              <Address>gtDate_CurrentHour</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>609</ID>
              <Description>"Freeze the date (time stops; edits above are kept)"</Description>
              <Color>00A5FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.feature('dateFreeze', true)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.feature('dateFreeze', false) end
{$asm}
</AssemblerScript>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>610</ID>
          <Description>"Unlock the whole map (all cities visited, discovery spots)"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec, function() GT.G.mapUnlock() end)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>611</ID>
          <Description>"Country parameters (check to list)"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.userRun(function() GT.G.listCountry(memrec) end)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.clearChildren(memrec) end
{$asm}
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>612</ID>
          <Description>"DataTable editor (check, then pick a table and a row)"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.userRun(function() GT.G.listDataTable(memrec) end)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.clearChildren(memrec) end
{$asm}
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>613</ID>
          <Description>"Object browser (check, then pick a class and an object)"</Description>
          <Options moHideChildren="1"/>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.userRun(function() GT.G.listObject(memrec) end)
{$asm}
[DISABLE]
{$lua}
if syntaxcheck then return end
if GT then GT.clearChildren(memrec) end
{$asm}
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>615</ID>
          <Description>"Unlock all Steam achievements"</Description>
          <Color>00A5FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
GT.steamUnlockAction(memrec)
{$asm}
[DISABLE]
</AssemblerScript>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
  </CheatEntries>
  <UserdefinedSymbols/>
  <LuaScript>--[[ =====================================================================================
  TheEndOfHistory.CT  -  Cheat Engine table for "The End of History" (Steam app 2953520)
  Framework: GameTemplate.CT V02 (namespace GT)
  Tested : Cheat Engine 7.7 (64-bit)
  Engine : Unreal Engine 5.7.4 / CL-51494982 (installed EXE version resource).
           Game data is read/written through UE reflection (GUObjectArray / FNamePool / FProperty),
           so gameplay property offsets are resolved by NAME at run time.
  Layout : [ATTACH / ALL OFF]  -&gt;  0. Settings &amp; README  -&gt;  1..8 game categories
===================================================================================== ]]
GT = GT or {}
local GT = GT

----------------------------------------------------------------- [GAME] basic settings
GT.TAG      = 'EOH'
GT.EXE      = 'History_Game-Win64-Shipping.exe'
GT.MODULE   = 'History_Game-Win64-Shipping.exe'
GT.GAMEVER  = 'Steam26248873'   -- installed Steam Build ID; in-game version string not confirmed
GT.BUILD    = {ts=0x1A9AE4D5, size=0x985E000}
GT.TICK_MS  = 500
GT.lang     = GT.lang or 'en'

----------------------------------------------------------------- common constants (do not change)
GT.C_GROUP  = 0xFFFF00
GT.C_SCRIPT = 0x00A5FF
GT.ID_TOP0   = 2
GT.OFF_VARS, GT.OFF_PTRS = 0x100, 0x200

----------------------------------------------------------------- [GAME] AOB signatures
-- [GAME] extension (reason: UE globals are only referenced through RIP-relative operands):
--   rip={dispOffset, instructionLength} : the result is the RIP-relative target, not the match itself.
--   multi=true : the pattern is a generic UE code idiom that appears many times; every match points to the
--                same global. The first match is used and the self-test checks that the targets agree.
GT.SIGS = {
  gobjects     = { p='48 8B 05 ?? ?? ?? ?? 48 8B 0C C8 48 8D 04 D1', rip={3,7}, multi=true },    -- mov rax,[GUObjectArray.ObjObjects.Objects]
  gnames       = { p='4C 8D 05 ?? ?? ?? ?? EB ?? 48 8D 0D ?? ?? ?? ?? E8', rip={3,7}, multi=true }, -- lea r8,[FNamePool]
  fmMalloc     = { p='48 89 5C 24 08 57 48 83 EC 20 48 8B F9 8B DA 48 8B 0D ?? ?? ?? ?? 48 85 C9 75 0C E8 ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? 48 8B 01 44 8B C3 48 8B D7 48 8B 5C 24 30 48 83 C4 20 5F 48 FF 60 28' }, -- FMemory::Malloc
  processEvent = { p='40 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8D 6C 24 ?? 48 89 9D ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C5 48 89 85 ?? ?? ?? ?? 8B 41 08 4D 8B F0 48 8B FA 4C 8B F9 0F BA E0 1E', off=0, len=8 }, -- UObject::ProcessEvent
}

----------------------------------------------------------------- [GAME] hooks
-- peGate: runs ONE queued function call on the game thread at the next UObject::ProcessEvent.
--   gtFlags+10 : 0 idle / 1 queued / 2 running / 3 done      gtVars+80..98 : rcx,rdx,r8,r9   gtVars+A0 : function   gtVars+A8 : rax
GT.HOOKS = {
  peGate = { sig='processEvent', aa=[==[
[ENABLE]
alloc(gt_pe,$1000,$SITE)
label(gt_pe_orig)
label(gt_pe_ret)
gt_pe:
  push rax
  push rdx
  mov al,1
  mov dl,2
  lock cmpxchg byte ptr [gtFlags+10],dl
  pop rdx
  pop rax
  jne gt_pe_orig
  push rcx
  push rdx
  push r8
  push r9
  push rbp
  mov rbp,rsp
  and rsp,-10
  sub rsp,20
  mov rcx,[gtVars+80]
  mov rdx,[gtVars+88]
  mov r8,[gtVars+90]
  mov r9,[gtVars+98]
  call qword ptr [gtVars+A0]
  mov [gtVars+A8],rax
  mov rsp,rbp
  pop rbp
  pop r9
  pop r8
  pop rdx
  pop rcx
  mov byte ptr [gtFlags+10],3
gt_pe_orig:
  $ORIG
  jmp gt_pe_ret
$SITE:
  jmp gt_pe
  $NOP
gt_pe_ret:
[DISABLE]
$SITE:
  $ORIG
dealloc(gt_pe)
]==] },
}

----------------------------------------------------------------- [GAME] parameter defaults (gtVars)
GT.VARDEF = {
  [0x20] = { 'float', 10.0 },     -- player movement speed multiplier (world map and realtime battle)
  [0x24] = { 'int',   0 },        -- ability EXP value (0 = maximum of DT_UnitExpSkillThreashold)
  [0x28] = { 'double', 99999.0 }, -- influence value
  [0x30] = { 'double', 9999999.0 },-- money value
  [0x38] = { 'int',   100 },      -- army morale
  [0x3C] = { 'float', 10.0 },     -- army mobility multiplier
  [0x40] = { 'int',   10 },       -- soldiers to add
  [0x44] = { 'int',   10 },       -- soldier level
  [0x48] = { 'double', 500.0 },   -- wall defence for "normalize"
  [0x50] = { 'int',   0 },        -- facility level override (0 = auto)
  [0x58] = { 'double', 5000.0 },  -- commerce
  [0x60] = { 'double', 2000.0 },  -- agriculture
  [0x68] = { 'double', 9999.0 },  -- food surplus (development max)
  [0x70] = { 'double', 9999.0 },  -- manpower (development max)
}

----------------------------------------------------------------- [GAME] features
GT.F = {
  invincible = { on=function() GT.G.startFast() end, task=function() GT.G.taskDmgPatch() GT.G.taskInvincible() GT.G.taskStamina() GT.G.taskHpGuard() end, off=function() GT.G.hpGuardOff() GT.G.dmgPatchOff() end },   -- HP / health / sanity / stamina
  pSpeed     = { task=function() GT.G.taskPSpeed() end, off=function() GT.G.restorePSpeed() end },
  influence  = { task=function() GT.G.taskFloor('Influence', readDouble(GT.var(0x28))) end },
  money      = { task=function() GT.G.taskFloor('Money', readDouble(GT.var(0x30))) end },
  age        = { on=function() GT.G.st.age=nil end, task=function() GT.G.taskAge() end },
  armyCost0  = { task=function() GT.G.taskArmy('cost') end },
  armySpeed  = { task=function() GT.G.taskArmySpeed() end, off=function() GT.G.restoreArmySpeed() end },
  armyGod    = { on=function() GT.G.st.shp={} GT.G.st.food={} GT.G.startFast() end, task=function() GT.G.taskDmgPatch() GT.G.taskArmyHP() GT.G.taskArmyStamina() GT.G.taskArmy('morale') GT.G.taskArmy('food') GT.G.taskFoodFreeze() end, off=function() GT.G.dmgPatchOff() end },  -- army invincible: HP / stamina / morale
  cityKeep   = { task=function() GT.G.normalizeOwnSpots(true) end },
  collapse0  = { task=function() GT.G.taskCollapse() end },
  warFatigue0= { task=function() GT.G.taskWarFatigue() end },
  enemyOneHit= { task=function() GT.G.taskEnemyOneHit() end },
  dateFreeze = { on=function() GT.G.dateFreezeOn() end, task=function() GT.G.dateFreezeTask() end, off=function() GT.G.st.dateFix=nil end },
}

----------------------------------------------------------------- [GAME] pointer slots (gtPtrs), refreshed by the timer
--   0 player unit / 1 player army / 2 selected person / 3 selected spot / 4 time owner (game mode)
--   5 player country / 6 player power / 7 selected object (browser)
GT.PTRS = {
  [0] = function() return GT.G.player('noscan') end,
  [1] = function() return GT.G.playerArmy('noscan') end,
  [2] = function() return GT.G.selValid('unit') end,
  [3] = function() return GT.G.selValid('spot') end,
  [4] = function() local o=GT.G.timeOwner('noscan') pcall(GT.G.regDateSyms,o) return o end,
  [5] = function() return GT.G.playerCountry('noscan') end,
  [6] = function() return GT.G.playerPower('noscan') end,
  [7] = function() return GT.G.selValid('obj') end,
}

----------------------------------------------------------------- [GAME] called once per attach (after init)
function GT.onAttach()
  GT.UE.reset()
  GT.G.reset()
  local ok,e=pcall(GT.UE.init)
  if not ok then GT.msg('UE reflection init failed: '..tostring(e),'UEリフレクションの初期化に失敗: '..tostring(e)) end
end

--=====================================================================================
--  COMMON PART  (no change needed per game)
--=====================================================================================
GT.A      = GT.A or {}      -- resolved signature addresses
GT.pend   = GT.pend or {}   -- unresolved signatures
GT.hookState = GT.hookState or {}
GT.featOn = GT.featOn or {}
GT.dyn    = GT.dyn or {}    -- dynamic records {mr,en,ja}
GT.suffix = GT.suffix or {} -- record ID -&gt; text appended to its name

----------------------------------------------------------------- helpers
local function rq(a) if not a or a==0 then return nil end local v=readQword(a) if not v or v==0 then return nil end return v end
local function ri(a) if not a or a==0 then return nil end local v=readInteger(a) if v and v&gt;=0x80000000 then v=v-0x100000000 end return v end
GT.rq, GT.ri = rq, ri
function GT.T(en,ja) if GT.lang=='ja' then return ja or en end return en end
function GT.msg(en,ja) print('['..GT.TAG..'] '..GT.T(en,ja)) end
function GT.err(en,ja) error(GT.T(en,ja),0) end
local function hex(n) return string.format('%X',n) end

function GT.mod(name) return getAddressSafe(name or GT.MODULE) end
function GT.modSize(name) local ok,s=pcall(getModuleSize,name or GT.MODULE) return ok and s or nil end
function GT.base() return getAddressSafe('gtFlags') end
function GT.flag(i) return GT.base()+i end
function GT.var(o) return GT.base()+GT.OFF_VARS+o end

----------------------------------------------------------------- one-shot / user action
-- run fn, show errors, then uncheck the record (action rows)
function GT.userRun(fn)
  local ok,e=pcall(fn)
  if not ok then local m=tostring(e) print('['..GT.TAG..'] '..m) pcall(showMessage,m) end
  return ok
end
function GT.oneShot(mr, fn)
  GT.userRun(fn)
  local t=createTimer(nil,false) t.Interval=150
  t.OnTimer=function(tt) tt.destroy() pcall(function() mr.Active=false end) end
  t.Enabled=true
end

----------------------------------------------------------------- attach / init
function GT.attached() return GT.pid~=nil and GT.pid==getOpenedProcessID() and GT.base()~=nil end
function GT.require()
  if not GT.attached() then
    GT.err('Not attached to '..GT.EXE..'. Run [ATTACH / ALL OFF] at the top of the table first.',
           GT.EXE..'にアタッチされていません。先にテーブル先頭の【アタッチ／一括解除】を実行してください。')
  end
end
-- attach CE to the game; returns true when the opened process changed
function GT.attachGame()
  local pid=getProcessIDFromProcessName(GT.EXE)
  if not pid or pid==0 then GT.err('The game is not running: '..GT.EXE,'ゲームが起動していません: '..GT.EXE) end
  local changed=false
  if getOpenedProcessID()~=pid then
    openProcess(pid) changed=true
    GT.msg(('Attached to %s (PID %d).'):format(GT.EXE,pid),('%s（PID %d）にアタッチしました。'):format(GT.EXE,pid))
  end
  if not GT.mod() then pcall(reinitializeSymbolhandler,true) end
  if not GT.mod() then GT.err(GT.MODULE..' is not loaded yet. Wait until the title screen and try again.',
                              GT.MODULE..' がまだ読み込まれていません。タイトル画面まで待ってから再実行してください。') end
  return changed
end
-- state of an old process: drop it without touching the new one
function GT.forgetProcess()
  GT.hookState={} GT.featOn={} GT.A={} GT.pend={}
  GT.pid=nil
end
function GT.checkBuild()
  if not GT.BUILD then return true end
  local m=GT.mod(); local lf=ri(m+0x3C); local ts=lf and readInteger(m+lf+8)
  local sz=GT.modSize()
  if ts~=GT.BUILD.ts or (GT.BUILD.size and sz~=GT.BUILD.size) then
    GT.msg(('WARNING: different game build (timestamp %s, size %s). Made for v%s. Features whose code is not found are disabled.'):format(hex(ts or 0),hex(sz or 0),GT.GAMEVER),
           ('警告: 対応ビルドと異なります（タイムスタンプ %s、サイズ %s）。対応バージョンは v%s です。コードが見つからない機能は無効になります。'):format(hex(ts or 0),hex(sz or 0),GT.GAMEVER))
    return false
  end
  return true
end
function GT.init()
  local pid=getOpenedProcessID()
  if GT.pid==pid and GT.base() then return true end
  GT.forgetProcess()
  for _,s in ipairs({'gtFlags','gtVars','gtPtrs'}) do pcall(unregisterSymbol,s) end
  -- near the module (within 2GB) so hook code can reach [gtFlags+n] RIP-relative.
  -- AA alloc searches free blocks near the address and retries; Lua allocateMemory(size,base) retries only once.
  local mem
  if autoAssemble(('alloc(gtFlags,$1000,%X)\nregistersymbol(gtFlags)\n'):format(GT.mod())) then mem=getAddressSafe('gtFlags') end
  if not mem or mem==0 then
    pcall(unregisterSymbol,'gtFlags')
    mem=allocateMemory(0x1000)
    if not mem or mem==0 then GT.err('Memory allocation failed.','メモリ確保に失敗しました。') end
    registerSymbol('gtFlags', mem, true)
    GT.msg('WARNING: shared memory is not near '..GT.MODULE..'. Hook features may fail to assemble.',
           '警告: 共有メモリを '..GT.MODULE..' の近くに確保できませんでした。フックを使う機能は組み立てに失敗する場合があります。')
  end
  registerSymbol('gtVars',  mem+GT.OFF_VARS, true)
  registerSymbol('gtPtrs',  mem+GT.OFF_PTRS, true)
  for o,d in pairs(GT.VARDEF) do
    -- [changed for The End of History] 'double' added (Blueprint "Float" variables are doubles in UE5)
    if d[1]=='float' then writeFloat(GT.var(o),d[2]) elseif d[1]=='double' then writeDouble(GT.var(o),d[2]) elseif d[1]=='qword' then writeQword(GT.var(o),d[2]) else writeInteger(GT.var(o),d[2]) end
  end
  GT.pid=pid
  GT.checkBuild()
  GT.resolveAll()
  pcall(GT.onAttach)
  GT.startTimer()
  return true
end

----------------------------------------------------------------- AOB resolver (synchronous aobscanregion)
local function aaFind(pat, s, e)
  pcall(unregisterSymbol,'gt_tmp')
  local ok=autoAssemble(('aobscanregion(gt_tmp,%s,%s,%s)\nregistersymbol(gt_tmp)\n'):format(hex(s),hex(e),pat))
  if not ok then return nil end
  local a=getAddressSafe('gt_tmp') pcall(unregisterSymbol,'gt_tmp')
  return a
end
local function aobAll(pat, s, e, maxn)
  local r={} local from=s
  while from&lt;e and #r&lt;maxn do local a=aaFind(pat,from,e) if not a then break end r[#r+1]=a from=a+1 end
  return r
end
function GT.resolveSig(name)
  if GT.A[name] then return GT.A[name] end
  local sg=GT.SIGS[name]; if not sg then GT.err('Unknown signature: '..name,'未定義のシグネチャ: '..name) end
  local m=GT.mod(sg.mod); local sz=GT.modSize(sg.mod)
  local a=m and sz and aaFind(sg.p, m, m+sz)
  if not a then GT.pend[name]=true return nil end
  -- [changed for The End of History] rip={dispOff,insnLen}: resolve the RIP-relative target (UE globals)
  if sg.rip then
    local d=ri(a+sg.rip[1]); if not d then GT.pend[name]=true return nil end
    a=a+sg.rip[2]+d
  else
    a=a+(sg.off or 0)
  end
  GT.A[name]=a; GT.pend[name]=nil
  pcall(unregisterSymbol,'gtA_'..name) registerSymbol('gtA_'..name, a, true)
  return a
end
function GT.resolveAll()
  local ok,ng=0,0
  for n in pairs(GT.SIGS) do if GT.resolveSig(n) then ok=ok+1 else ng=ng+1 end end
  GT.msg(('Signatures: %d found, %d not found.'):format(ok,ng),('シグネチャ: %d件検出、%d件未検出。'):format(ok,ng))
end
function GT.pendingNames() local r={} for n in pairs(GT.pend) do r[#r+1]=n end table.sort(r) return r end

----------------------------------------------------------------- hook manager
function GT.hookOn(name)
  if GT.hookState[name] then return end
  local h=GT.HOOKS[name] or GT.err('Unknown hook: '..name,'未定義のフック: '..name)
  local a=GT.resolveSig(h.sig)
  if not a then GT.err('Game code for this feature was not found: '..h.sig..'\n(it may not be loaded yet, or the game was updated)',
                       'この機能に必要なゲームコードが見つかりません: '..h.sig..'\n（未読み込み、またはゲーム更新で変更された可能性があります）') end
  local len=GT.SIGS[h.sig].len or 5
  local orig=readBytes(a,len,true) or GT.err('Cannot read hook site.','フック位置を読み取れません。')
  local db={} for i,b in ipairs(orig) do db[i]=string.format('%02X',b) end
  local s=h.aa:gsub('%$SITE','gtA_'..h.sig):gsub('%$ORIG','db '..table.concat(db,' '))
  s=s:gsub('%$NOP', len&gt;5 and ('nop '..(len-5)) or '')
  local ok,info=autoAssemble(s)
  if not ok then GT.err('Hook assembly failed: '..name..'\n'..tostring(info),'フックの組み立てに失敗: '..name..'\n'..tostring(info)) end
  GT.hookState[name]={script=s, info=info, addr=a, orig=orig}
end
function GT.hookOff(name)
  local st=GT.hookState[name]; if not st then return end
  if GT.attached() then
    local ok=autoAssemble(st.script, st.info)
    if not ok then writeBytes(st.addr, st.orig) end   -- fallback: restore original bytes
  end
  GT.hookState[name]=nil
end

----------------------------------------------------------------- features
function GT.feature(key, on)
  local f=GT.F[key] or GT.err('Unknown feature: '..key,'未定義の機能: '..key)
  if on then
    GT.require()
    for _,h in ipairs(f.hooks or {}) do GT.hookOn(h) end
    if f.flag then writeBytes(GT.flag(f.flag),1) end
    if f.on then f.on() end
    GT.featOn[key]=true
  else
    GT.featOn[key]=nil
    if not GT.attached() then return end
    if f.flag then writeBytes(GT.flag(f.flag),0) end
    if f.off then pcall(f.off) end
    for _,h in ipairs(f.hooks or {}) do
      local used=false
      for k in pairs(GT.featOn) do for _,x in ipairs(GT.F[k].hooks or {}) do if x==h then used=true end end end
      if not used then GT.hookOff(h) end
    end
  end
end

----------------------------------------------------------------- timer (pointer slots, tasks, process watch)
function GT.tick()
  if not GT.pid then return end
  if getOpenedProcessID()~=GT.pid or getProcessIDFromProcessName(GT.EXE)~=GT.pid then
    GT.msg('Game process changed or exited. Run [ATTACH / ALL OFF] again.','ゲームプロセスが変わったか終了しました。【アタッチ／一括解除】を再実行してください。')
    GT.forgetProcess() return
  end
  local b=GT.base(); if not b then return end
  for slot,fn in pairs(GT.PTRS) do local ok,a=pcall(fn) writeQword(b+GT.OFF_PTRS+slot*8, (ok and a) or 0) end
  for k in pairs(GT.featOn) do local f=GT.F[k] if f.task then pcall(f.task) end end
  for n in pairs(GT.pend) do GT.resolveSig(n) end
end
function GT.startTimer()
  if GT.timer then return end
  GT.timer=createTimer(nil,false) GT.timer.Interval=GT.TICK_MS
  GT.timer.OnTimer=function() pcall(GT.tick) end
  GT.timer.Enabled=true
end

----------------------------------------------------------------- ATTACH / ALL OFF
function GT.disableAll(self)
  local ok,changed=pcall(GT.attachGame)
  if not ok then print('['..GT.TAG..'] '..tostring(changed)) pcall(showMessage,tostring(changed)) return end
  if changed or (GT.pid and GT.pid~=getOpenedProcessID()) then GT.forgetProcess() end
  local okI,e=pcall(GT.init) if not okI then print('['..GT.TAG..'] '..tostring(e)) pcall(showMessage,tostring(e)) return end
  local al=getAddressList(); local n=0
  for i=al.Count-1,0,-1 do
    local m=al.getMemoryRecord(i)
    if m.Active and not (self and m.ID==self.ID) then pcall(function() m.Active=false end) n=n+1 end
  end
  local r=0 for name in pairs(GT.hookState) do GT.hookOff(name) r=r+1 end
  for i=0,0xFF do writeBytes(GT.flag(i),0) end
  GT.featOn={}
  GT.msg(('Attach / All OFF: %d records deactivated, %d hooks restored.'):format(n,r),
         ('アタッチ／一括解除: %d件を無効化、%d件のフックを元に戻しました。'):format(n,r))
end

----------------------------------------------------------------- UI helpers
-- dynamic records (lists built at run time)
function GT.clearChildren(mr) while mr.Count&gt;0 do mr.Child[0].destroy() end end
function GT.addHdr(parent, en, ja)
  local m=getAddressList().createMemoryRecord()
  m.IsGroupHeader=true m.Description=GT.T(en,ja) m.Color=GT.C_GROUP m.appendToEntry(parent) m.DontSave=true
  GT.dyn[#GT.dyn+1]={mr=m,en=en,ja=ja} return m
end
function GT.addRec(parent, en, ja, addr, vt)
  local m=getAddressList().createMemoryRecord()
  m.Description=GT.T(en,ja) m.Type=vt or vtDword m.Address=addr m.appendToEntry(parent) m.DontSave=true
  GT.dyn[#GT.dyn+1]={mr=m,en=en,ja=ja} return m
end

----------------------------------------------------------------- language
function GT.applyLang()
  local al=getAddressList()
  for i=0,al.Count-1 do
    local m=al.getMemoryRecord(i); local n=GT.NAMES[m.ID]
    if n then local d=(GT.lang=='ja') and n[2] or n[1]
      if GT.suffix[m.ID] then d=d..' : '..GT.suffix[m.ID] end
      m.Description=d end
  end
  local keep={}
  for _,e in ipairs(GT.dyn) do if pcall(function() e.mr.Description=GT.T(e.en,e.ja) end) then keep[#keep+1]=e end end
  GT.dyn=keep
end
function GT.setLang(l) GT.lang=l GT.applyLang() end

----------------------------------------------------------------- diagnostics
-- Lua Engine window: record ID 13 is a toggle (check = open / uncheck = close)
function GT.luaEngine() local ok,f=pcall(getLuaEngine) if ok then return f end end
function GT.openLuaEngine(quiet)
  local f=GT.luaEngine()
  if f then pcall(function() f.show() end) pcall(function() f.bringToFront() end) end
  if not quiet then GT.msg('Lua Engine opened. Messages of this table are printed here.','Lua Engineを開きました。このテーブルのメッセージはここに表示されます。') end
end
function GT.closeLuaEngine()
  local f=GT.luaEngine()
  if f then pcall(function() f.hide() end) end
end
function GT.envReport()
  GT.openLuaEngine(true)
  local pid=getOpenedProcessID()
  print(('[%s] CE version      : %s'):format(GT.TAG, tostring(getCEVersion and getCEVersion())))
  print(('[%s] target process  : %s (running PID %s / opened PID %s / table PID %s)'):format(GT.TAG, GT.EXE,
        tostring(getProcessIDFromProcessName(GT.EXE)), tostring(pid), tostring(GT.pid)))
  local okx,x64=pcall(targetIs64Bit) print(('[%s] target 64-bit   : %s'):format(GT.TAG, tostring(okx and x64)))
  local m=GT.mod()
  if m then
    local lf=ri(m+0x3C); local ts=lf and readInteger(m+lf+8)
    print(('[%s] module          : %s base=%X size=%X PE-timestamp=%X'):format(GT.TAG, GT.MODULE, m, GT.modSize() or 0, ts or 0))
    if GT.BUILD then print(('[%s] expected build  : timestamp=%X size=%X (v%s)'):format(GT.TAG, GT.BUILD.ts, GT.BUILD.size or 0, GT.GAMEVER)) end
  else print(('[%s] module          : %s NOT FOUND'):format(GT.TAG, GT.MODULE)) end
  print(('[%s] shared memory   : gtFlags=%s'):format(GT.TAG, GT.base() and hex(GT.base()) or 'not allocated'))
  print(('[%s] attached        : %s'):format(GT.TAG, tostring(GT.attached())))
end
function GT.scanSelfTest()
  GT.openLuaEngine(true) GT.require()
  local okn,bad=0,0
  local names={} for n in pairs(GT.SIGS) do names[#names+1]=n end table.sort(names)
  for _,name in ipairs(names) do
    local sg=GT.SIGS[name]; local m=GT.mod(sg.mod); local sz=GT.modSize(sg.mod)
    local r=aobAll(sg.p, m, m+sz, 3)
    local got={} for i,a in ipairs(r) do got[i]=string.format('%s+%X',sg.mod or GT.MODULE,a-m) end
    local st=(#r==1) and 'OK' or ((#r==0) and 'NOT FOUND' or 'NOT UNIQUE')
    -- [changed for The End of History] multi=true: several matches are fine when all RIP targets agree
    if sg.multi and sg.rip and #r&gt;1 then
      local tg={} local same=true
      for i,a in ipairs(r) do local d=ri(a+sg.rip[1]) tg[i]=d and (a+sg.rip[2]+d) if tg[i]~=tg[1] then same=false end end
      if same and tg[1] then st='OK (multi)' got={string.format('-&gt; %X',tg[1])} end
    end
    if st:sub(1,2)=='OK' then okn=okn+1 else bad=bad+1 end
    print(('[%s]   %-16s %-10s %s'):format(GT.TAG, name, st, table.concat(got,', ')))
  end
  GT.msg(('AOB self-test done: %d OK, %d NG.'):format(okn,bad),('AOBスキャンテスト完了: 正常 %d件、異常 %d件。'):format(okn,bad))
end
function GT.statusReport()
  GT.openLuaEngine(true)
  local l=GT.pendingNames()
  print(('[%s] unresolved signatures: %s'):format(GT.TAG, #l&gt;0 and table.concat(l,', ') or '-'))
  local h={} for n,st in pairs(GT.hookState) do h[#h+1]=('%s@%X'):format(n,st.addr) end table.sort(h)
  print(('[%s] active hooks        : %s'):format(GT.TAG, #h&gt;0 and table.concat(h,', ') or '-'))
  local f={} for k in pairs(GT.featOn) do f[#f+1]=k end table.sort(f)
  print(('[%s] features on         : %s'):format(GT.TAG, #f&gt;0 and table.concat(f,', ') or '-'))
  local al=getAddressList(); local a={}
  for i=0,al.Count-1 do local m=al.getMemoryRecord(i) if m.Active then a[#a+1]=('[%d] %s'):format(m.ID,m.Description) end end
  print(('[%s] active records      : %s'):format(GT.TAG, #a&gt;0 and table.concat(a,' / ') or '-'))
end


--=====================================================================================
--  [GAME] UE REFLECTION LAYER  (Unreal Engine 5.x; layouts are detected at run time)
--=====================================================================================
GT.UE = GT.UE or {}
local UE = GT.UE
local rq, ri = GT.rq, GT.ri
local function hx(n) return string.format('%X', n) end

function UE.reset()
  UE.L=nil UE.names={} UE.byCls=nil UE.byName=nil UE.scanT=-1e9 UE.scanN=0
  UE.pc={} UE.plist={} UE.podc={}
end
if not UE.names then UE.reset() end

----------------------------------------------------------------- UTF-16 helpers
function UE.u16dec(b, n)
  local out={} local i=1 local lim=math.min(#b, n*2)
  while i+1&lt;=lim do
    local c=b[i]|(b[i+1]&lt;&lt;8) i=i+2
    if c==0 then break end
    if c&gt;=0xD800 and c&lt;=0xDBFF and i+1&lt;=lim then
      local c2=b[i]|(b[i+1]&lt;&lt;8)
      if c2&gt;=0xDC00 and c2&lt;=0xDFFF then i=i+2 c=0x10000+((c-0xD800)&lt;&lt;10)+(c2-0xDC00) end
    end
    out[#out+1]=utf8.char(c)
  end
  return table.concat(out)
end
function UE.u16enc(s)   -- byte table incl. terminator, number of UTF-16 units incl. terminator
  local b={} local n=0
  for _,cp in utf8.codes(s) do
    if cp&gt;=0x10000 then
      cp=cp-0x10000 local hi=0xD800+(cp&gt;&gt;10) local lo=0xDC00+(cp&amp;0x3FF)
      b[#b+1]=hi&amp;0xFF b[#b+1]=hi&gt;&gt;8 b[#b+1]=lo&amp;0xFF b[#b+1]=lo&gt;&gt;8 n=n+2
    else b[#b+1]=cp&amp;0xFF b[#b+1]=(cp&gt;&gt;8)&amp;0xFF n=n+1 end
  end
  b[#b+1]=0 b[#b+1]=0
  return b, n+1
end
local function le(v,n) local t={} for i=1,n do t[i]=v&amp;0xFF v=v&gt;&gt;8 end return t end
local function put(t,off,bytes) for i=1,#bytes do t[off+i]=bytes[i] end end
local function zeros(n) local t={} for i=1,n do t[i]=0 end return t end
local function align(v,a) return (v+a-1)//a*a end
UE.le = le

----------------------------------------------------------------- FName
function UE.nameStr(idx, num)
  if not idx then return nil end
  local s=UE.names[idx]
  if not s then
    local L=UE.L
    if idx&lt;0 or (idx&gt;&gt;16)&gt;=8192 then return nil end
    local blk=rq(L.pool+L.blocksOff+(idx&gt;&gt;16)*8); if not blk then return nil end
    local e=blk+(idx&amp;0xFFFF)*2
    local h=readSmallInteger(e); if not h then return nil end
    h=h&amp;0xFFFF
    local len=h&gt;&gt;L.lenShift
    if len&lt;=0 or len&gt;1024 then return nil end
    if (h&amp;1)==1 then
      local b=readBytes(e+2,len*2,true); if not b then return nil end
      s=UE.u16dec(b,len)
    else
      local b=readBytes(e+2,len,true); if not b then return nil end
      s=string.char(table.unpack(b))
    end
    UE.names[idx]=s
  end
  if num and num&gt;0 then return s..'_'..(num-1) end
  return s
end
function UE.fname(a) local i=readInteger(a) if not i then return nil end return UE.nameStr(i, readInteger(a+4)) end

----------------------------------------------------------------- UObject basics (UE5: Flags+8 Index+C Class+10 Name+18 Outer+20)
function UE.objName(o) if not o or o==0 then return nil end return UE.fname(o+0x18) end
function UE.className(o) local c=rq(o+0x10) return c and UE.objName(c) end
function UE.path(o)
  local t={} local g=0
  while o and o~=0 and g&lt;16 do t[#t+1]=UE.objName(o) or '?' o=rq(o+0x20) g=g+1 end
  local r={} for i=#t,1,-1 do r[#r+1]=t[i] end
  return table.concat(r,'.')
end
function UE.isCDO(o) local f=readInteger(o+8) return (not f) or (f&amp;0x30)~=0 end

----------------------------------------------------------------- GUObjectArray
function UE.numObjects() return readInteger(UE.L.objs+0x14) end
function UE.objAt(i)
  local L=UE.L
  local ch=rq(rq(L.objs) and (rq(L.objs)+(i&gt;&gt;16)*8)); if not ch then return nil end
  return rq(ch+(i&amp;0xFFFF)*L.item+L.itemObj)
end
-- one pass over all objects: byCls[classPtr] = {objects}, byName[className] = {{cls=,objs=},..}
function UE.scan(force)
  if not UE.L then GT.err('UE reflection is not initialized (attach again).','UEリフレクション未初期化です（再アタッチしてください）。') end
  if not force and UE.byCls then return end      -- full scans only on demand (user actions / stale cache)
  local L=UE.L
  local n=UE.numObjects() or 0
  local arr=rq(L.objs)
  if not arr or n&lt;=0 or n&gt;0x4000000 then GT.err('GUObjectArray is invalid.','GUObjectArrayが不正です。') end
  local byCls={}
  local isz=L.item
  for c=0,(n-1)&gt;&gt;16 do
    local ch=rq(arr+c*8)
    if ch then
      local lim=math.min(0x10000, n-c*0x10000)-1
      for j=0,lim do
        local o=readQword(ch+j*isz+L.itemObj)
        if o and o~=0 then
          local cl=readQword(o+0x10)
          if cl and cl~=0 then
            local t=byCls[cl] if not t then t={} byCls[cl]=t end
            t[#t+1]=o
          end
        end
      end
    end
  end
  local byName={}
  for cl,list in pairs(byCls) do
    local nm=UE.objName(cl)
    if nm then local e=byName[nm] if not e then e={} byName[nm]=e end e[#e+1]={cls=cl,objs=list} end
  end
  UE.byCls=byCls UE.byName=byName UE.scanT=getTickCount() UE.scanN=n
end
-- non-CDO instances of all classes named cname
function UE.instances(cname, force)
  UE.scan(force)
  local r={}
  for _,e in ipairs(UE.byName[cname] or {}) do
    for _,o in ipairs(e.objs) do if not UE.isCDO(o) then r[#r+1]=o end end
  end
  return r
end
-- object of class &lt;className&gt; with name &lt;objName&gt; (and outer name)
function UE.findObject(className, objName, outerName)
  UE.scan()
  for _,e in ipairs(UE.byName[className] or {}) do
    for _,o in ipairs(e.objs) do
      if UE.objName(o)==objName and (not outerName or UE.objName(rq(o+0x20) or 0)==outerName) then return o end
    end
  end
end
UE.CLASSKINDS={'Class','BlueprintGeneratedClass','WidgetBlueprintGeneratedClass','AnimBlueprintGeneratedClass'}
function UE.findClass(name, outerName)
  for _,k in ipairs(UE.CLASSKINDS) do local o=UE.findObject(k,name,outerName) if o then return o end end
end
function UE.cdo(cls)
  UE.scan()
  for _,o in ipairs(UE.byCls[cls] or {}) do local f=readInteger(o+8) if f and (f&amp;0x10)~=0 then return o end end
end
function UE.childObject(className, name, outerObj)
  UE.scan()
  for _,e in ipairs(UE.byName[className] or {}) do
    for _,o in ipairs(e.objs) do if rq(o+0x20)==outerObj and UE.objName(o)==name then return o end end
  end
end

----------------------------------------------------------------- FField / FProperty
function UE.fnameOf(f) return UE.fname(f+UE.L.fName) end
function UE.ftype(f) local fc=rq(f+UE.L.fClass) return fc and UE.fname(fc) end
local function ownerOf(f) local v=rq(f+0x10) return v and (v &amp; ~7) end
local function cleanName(n) return (n:gsub('_%d+_'..('%x'):rep(32)..'$','')) end
UE.cleanName=cleanName
-- all FProperties of a UStruct (own first, then supers)
function UE.fields(st)
  local c=UE.plist[st]; if c then return c end
  local L=UE.L local list={} local s=st local g=0
  while s and s~=0 and g&lt;32 do
    local f=rq(s+L.childProps) local k=0
    while f and f~=0 and k&lt;4096 do list[#list+1]=f f=rq(f+L.fNext) k=k+1 end
    s=rq(s+L.super) g=g+1
  end
  UE.plist[st]=list
  return list
end
function UE.ownedAt(f, k)
  local n=0
  for o=UE.L.pBase, UE.L.pBase+0x20, 8 do
    local q=rq(f+o)
    if q and ownerOf(q)==f then
      if n==k then return UE.info(q) end
      n=n+1
    end
  end
end
function UE.info(f)
  local L=UE.L
  local p={f=f, name=UE.fnameOf(f) or '?', t=UE.ftype(f) or '?', off=ri(f+L.pOff) or 0, size=ri(f+L.pSize) or 0, dim=ri(f+L.pDim) or 1}
  p.cname=cleanName(p.name)
  local t=p.t
  if t=='BoolProperty' then
    local b=readBytes(f+L.pBase,4,true) or {1,0,1,0xFF}
    p.bOff=b[2] p.bMask=b[3] p.fMask=b[4]
    if p.fMask==0 then p.fMask=0xFF end
    if p.bMask==0 then p.bMask=1 end
  elseif t=='StructProperty' then p.struct=rq(f+L.pBase) p.sname=p.struct and UE.objName(p.struct)
  elseif t=='ArrayProperty' or t=='SetProperty' then p.inner=UE.ownedAt(f,0)
  elseif t=='MapProperty' then p.key=UE.ownedAt(f,0) p.val=UE.ownedAt(f,1)
  elseif t=='ObjectProperty' or t=='ClassProperty' or t=='WeakObjectProperty' then p.cls=rq(f+L.pBase)
  elseif t=='EnumProperty' then p.under=UE.ownedAt(f,0)
  end
  return p
end
-- property info by name in a UStruct (exact, then without the BP "_N_GUID" suffix, then case-insensitive)
function UE.PS(st, name)
  if not st then return nil end
  if name:find('|',1,true) then
    for n in name:gmatch('[^|]+') do local p=UE.PS(st,n) if p then return p end end
    return nil
  end
  local key=st..'|'..name
  local c=UE.pc[key]
  if c~=nil then return c or nil end
  local list=UE.fields(st)
  local hit
  for _,f in ipairs(list) do if UE.fnameOf(f)==name then hit=f break end end
  if not hit then for _,f in ipairs(list) do local n=UE.fnameOf(f) if n and cleanName(n)==name then hit=f break end end end
  if not hit then local ln=name:lower() for _,f in ipairs(list) do local n=UE.fnameOf(f) if n and cleanName(n):lower()==ln then hit=f break end end end
  if not hit then local ln=name:lower():gsub(' ','') for _,f in ipairs(list) do local n=UE.fnameOf(f) if n and cleanName(n):lower():gsub(' ','')==ln then hit=f break end end end
  local p=hit and UE.info(hit) or false
  UE.pc[key]=p
  return p or nil
end
function UE.P(o, name) local c=o and rq(o+0x10) if not c then return nil end return UE.PS(c, name) end
function UE.allInfo(st) local r={} for _,f in ipairs(UE.fields(st)) do r[#r+1]=UE.info(f) end return r end

----------------------------------------------------------------- value read / write by property
function UE.fstr(a)
  local d=rq(a) local n=ri(a+8)
  if not d or not n or n&lt;=0 or n&gt;0x10000 then return '' end
  local b=readBytes(d,n*2,true); if not b then return '' end
  return UE.u16dec(b,n)
end
function UE.rd(a, p)
  local t=p.t
  if t=='IntProperty' then return ri(a)
  elseif t=='DoubleProperty' then return readDouble(a)
  elseif t=='FloatProperty' then return readFloat(a)
  elseif t=='BoolProperty' then local b=readBytes(a+p.bOff,1,false) return b~=nil and (b &amp; p.fMask)~=0
  elseif t=='ByteProperty' then return readBytes(a,1,false)
  elseif t=='EnumProperty' then if p.size==1 then return readBytes(a,1,false) elseif p.size==2 then return readSmallInteger(a) else return ri(a) end
  elseif t=='Int64Property' or t=='UInt64Property' then return readQword(a)
  elseif t=='UInt32Property' then return readInteger(a)
  elseif t=='Int16Property' or t=='UInt16Property' then return readSmallInteger(a)
  elseif t=='StrProperty' then return UE.fstr(a)
  elseif t=='NameProperty' then return UE.fname(a)
  elseif t=='ObjectProperty' or t=='ClassProperty' then return rq(a)
  end
  return nil
end
function UE.wr(a, p, v)
  local t=p.t
  if t=='IntProperty' or t=='UInt32Property' then writeInteger(a, math.floor(v))
  elseif t=='DoubleProperty' then writeDouble(a, v)
  elseif t=='FloatProperty' then writeFloat(a, v)
  elseif t=='BoolProperty' then
    local b=readBytes(a+p.bOff,1,false) or 0
    if v then b=b|p.bMask else b=b&amp;(~p.bMask)&amp;0xFF end
    writeBytes(a+p.bOff, b)
  elseif t=='ByteProperty' or (t=='EnumProperty' and p.size==1) then writeBytes(a, math.floor(v)&amp;0xFF)
  elseif t=='EnumProperty' then writeInteger(a, math.floor(v))
  elseif t=='Int64Property' or t=='UInt64Property' then writeQword(a, math.floor(v))
  elseif t=='StrProperty' then UE.setFStr(a, v)
  elseif t=='ObjectProperty' then writeQword(a, v)
  else GT.err('Unsupported property type: '..t,'未対応のプロパティ型: '..t) end
end
function UE.get(o, name) local p=UE.P(o,name) if not p then return nil end return UE.rd(o+p.off,p), p end
function UE.set(o, name, v) local p=UE.P(o,name) if not p then return false end UE.wr(o+p.off,p,v) return true end
function UE.addr(o, name) local p=UE.P(o,name) if not p then return nil end return o+p.off, p end

----------------------------------------------------------------- game-side memory (FMemory::Malloc) and atomic writes
function UE.malloc(size)
  local fn=GT.A.fmMalloc or GT.resolveSig('fmMalloc')
  if not fn then GT.err('FMemory::Malloc was not found (game updated?).','FMemory::Mallocが見つかりません（ゲーム更新の可能性）。') end
  local r=executeCodeEx(0, 5000, fn, size, 0)
  if not r or r==0 then GT.err('Game-side memory allocation failed.','ゲーム側のメモリ確保に失敗しました。') end
  writeBytes(r, zeros(size))
  return r
end
-- suspend the game while several dependent fields are rewritten
function UE.atomic(fn)
  pause()
  local ok,e=pcall(fn)
  unpause()
  if not ok then error(e,0) end
end
-- FString {TCHAR* Data; int32 Num; int32 Max}
function UE.setFStr(a, s)
  s=s or ''
  if s=='' then if (ri(a+8) or 0)&gt;0 then writeInteger(a+8,0) end return end
  local b,n=UE.u16enc(s)
  local d=rq(a) local mx=ri(a+12) or 0
  if d and mx&gt;=n then writeBytes(d,b) writeInteger(a+8,n) return end
  local nd=UE.malloc(n*2)
  writeBytes(nd,b)
  UE.atomic(function() writeQword(a,nd) writeInteger(a+8,n) writeInteger(a+12,n) end)   -- old buffer intentionally not freed
end
function UE.newFStr(s)   -- 16 bytes of a fresh FString
  if not s or s=='' then return zeros(16) end
  local b,n=UE.u16enc(s)
  local d=UE.malloc(n*2) writeBytes(d,b)
  local t=le(d,8) put(t,8,le(n,4)) put(t,12,le(n,4))
  return t
end

----------------------------------------------------------------- TArray {Data; int32 Num; int32 Max}
function UE.arr(a) return rq(a) or 0, ri(a+8) or 0, ri(a+12) or 0 end
function UE.arrAppend(a, esz, bytes)
  local d,n,m=UE.arr(a)
  if d~=0 and n&lt;m then
    writeBytes(d+n*esz, bytes)
    writeInteger(a+8, n+1)
    return d+n*esz
  end
  local nm=math.max(4, m*2, n+1)
  local nd=UE.malloc(nm*esz)
  local res
  UE.atomic(function()
    local d2,n2=UE.arr(a)
    if n2+1&gt;nm then error('array changed',0) end
    if n2&gt;0 then writeBytes(nd, readBytes(d2, n2*esz, true)) end
    writeBytes(nd+n2*esz, bytes)
    writeQword(a, nd) writeInteger(a+12, nm) writeInteger(a+8, n2+1)
    res=nd+n2*esz
  end)
  return res      -- the old buffer is intentionally not freed (game code may still hold it)
end
function UE.arrRemove(a, esz, i)
  UE.atomic(function()
    local d,n=UE.arr(a)
    if i&lt;0 or i&gt;=n then return end
    if i&lt;n-1 then writeBytes(d+i*esz, readBytes(d+(i+1)*esz, (n-1-i)*esz, true)) end
    writeInteger(a+8, n-1)
  end)
end
function UE.strArr(o, name)
  local a=UE.addr(o,name); if not a then return nil end
  local d,n=UE.arr(a) local r={}
  for i=0,n-1 do r[#r+1]=UE.fstr(d+i*16) end
  return r, a
end
function UE.strArrHas(o, name, s) local l=UE.strArr(o,name) if l then for _,x in ipairs(l) do if x==s then return true end end end return false end
function UE.strArrAdd(o, name, s)
  local a=UE.addr(o,name); if not a then GT.err('Property not found: '..name,'プロパティが見つかりません: '..name) end
  if UE.strArrHas(o,name,s) then return false end
  UE.arrAppend(a,16,UE.newFStr(s)) return true
end
function UE.strArrRemove(o, name, s)
  local a=UE.addr(o,name); if not a then return false end
  local d,n=UE.arr(a)
  for i=0,n-1 do if UE.fstr(d+i*16)==s then UE.arrRemove(a,16,i) return true end end
  return false
end

----------------------------------------------------------------- TMap (TSet&lt;TPair&gt; over TSparseArray, UE5 layout)
--  +00 Data +08 Num +0C Max | +10 AllocFlags inline[4] +20 secondary +28 NumBits +2C MaxBits | +30 FirstFree +34 NumFree
--  +38 Hash inline +40 Hash secondary +48 HashSize        (verified at run time before any insertion)
local function alignOf(p)
  local t=p.t
  if t=='BoolProperty' or t=='ByteProperty' or (t=='EnumProperty' and p.size==1) then return 1 end
  if t=='IntProperty' or t=='FloatProperty' or t=='UInt32Property' or t=='EnumProperty' then return 4 end
  return 8
end
function UE.mapLayout(p)
  if not p or not p.key or not p.val then return nil end
  local ka,va=alignOf(p.key),alignOf(p.val)
  local ma=math.max(ka,va)
  local voff=align(p.key.size, va)
  local pair=align(voff+p.val.size, ma)
  local hn=align(pair,4)
  return {voff=voff, hn=hn, hi=hn+4, es=align(hn+8, math.max(ma,4))}
end
function UE.mapEntries(a, p)
  local lay=UE.mapLayout(p); if not lay then return {} end
  local d,n=UE.arr(a)
  local sec=rq(a+0x20)
  local bits=(sec and sec~=0) and sec or (a+0x10)
  local out={}
  for i=0,n-1 do
    local w=readInteger(bits+(i&gt;&gt;5)*4) or 0
    if ((w&gt;&gt;(i&amp;31))&amp;1)==1 then out[#out+1]={i=i, e=d+i*lay.es, v=d+i*lay.es+lay.voff} end
  end
  return out, lay
end
function UE.mapFindStr(a, p, key)
  local ents=UE.mapEntries(a,p)
  for _,e in ipairs(ents) do if UE.fstr(e.e)==key then return e.v end end
end
local CRC
local function crcTable()
  if CRC then return CRC end
  CRC={}
  for i=0,255 do
    local c=i&lt;&lt;24
    for _=1,8 do if (c&amp;0x80000000)~=0 then c=((c&lt;&lt;1)~0x04C11DB7)&amp;0xFFFFFFFF else c=(c&lt;&lt;1)&amp;0xFFFFFFFF end end
    CRC[i]=c
  end
  return CRC
end
-- FCrc::Strihash_DEPRECATED (GetTypeHash(FString)); ASCII upper-casing only
function UE.strihash(s)
  local T=crcTable() local h=0
  local b,n=UE.u16enc(s)
  for i=1,(n-1)*2,2 do
    local ch=b[i]|(b[i+1]&lt;&lt;8)
    if ch&gt;=0x61 and ch&lt;=0x7A then ch=ch-0x20 end
    h=((h&gt;&gt;8)&amp;0x00FFFFFF)~T[(h~(ch&amp;0xFF))&amp;0xFF]
    h=((h&gt;&gt;8)&amp;0x00FFFFFF)~T[(h~(ch&gt;&gt;8))&amp;0xFF]
  end
  return h
end
-- check that our hash / layout assumptions match the existing map exactly
function UE.mapVerify(a, p)
  local ents,lay=UE.mapEntries(a,p)
  if not lay or p.key.t~='StrProperty' then return false,'key type' end
  if (ri(a+0x28) or -1)&lt;(ri(a+8) or 0) then return false,'bits' end
  local hs=ri(a+0x48) or 0
  if hs==0 and (ri(a+8) or 0)==0 then return true, lay end      -- empty map: hash not allocated yet
  if hs&lt;=0 or (hs&amp;(hs-1))~=0 then return false,'hash size '..hs end
  for _,e in ipairs(ents) do
    local h=UE.strihash(UE.fstr(e.e))
    if (ri(e.e+lay.hi) or -1)~=(h&amp;(hs-1)) then return false,'hash mismatch' end
  end
  return true, lay
end
-- add or replace an entry with an FString key; valBytes = bytes of the value
function UE.mapSetStr(a, p, key, valBytes)
  local v=UE.mapFindStr(a,p,key)
  if v then writeBytes(v, valBytes) return 'updated' end
  local ok,lay=UE.mapVerify(a,p)
  if not ok then GT.err('Map layout check failed ('..tostring(lay)..'). Entry not added.','マップ構造の確認に失敗（'..tostring(lay)..'）。追加を中止しました。') end
  local es=lay.es
  local kb=UE.newFStr(key)
  local h=UE.strihash(key)
  local d,n,m=UE.arr(a)
  local nd
  if (ri(a+0x34) or 0)==0 and n&gt;=m then nd=UE.malloc(math.max(4,m*2)*es) end
  local elem=zeros(es) put(elem,0,kb) put(elem,lay.voff,valBytes)
  -- allocation-flag bit array full: pre-allocate a bigger heap buffer (moved from inline storage if needed)
  local nbits
  local nb0,mb0=ri(a+0x28) or 0, ri(a+0x2C) or 0
  if (ri(a+0x34) or 0)==0 and nb0+1&gt;math.max(mb0, ((rq(a+0x20) or 0)==0) and 128 or 0) then
    nbits={max=math.max(256, mb0*2)}
    nbits.buf=UE.malloc(nbits.max//8)
  end
  UE.atomic(function()
    local d2,n2,m2=UE.arr(a)
    if (ri(a+0x48) or 0)==0 then
      -- empty map: create a 1-bucket inline hash (the game rehashes to a proper size on its next add)
      if n2~=0 then error('hash missing',0) end
      writeInteger(a+0x38,-1) writeQword(a+0x40,0) writeInteger(a+0x48,1)
      if (rq(a+0x20) or 0)==0 and (ri(a+0x2C) or 0)&lt;128 then writeInteger(a+0x2C,128) end
    end
    local hs=ri(a+0x48)
    local hsec=rq(a+0x40)
    local hb=(hsec and hsec~=0) and hsec or (a+0x38)
    local nfree=ri(a+0x34) or 0
    local idx
    if nfree&gt;0 then
      idx=ri(a+0x30)
      local nxt=ri(d2+idx*es+4)
      writeInteger(a+0x30,nxt)
      writeInteger(a+0x34,nfree-1)
      if nfree-1&gt;0 and nxt&gt;=0 then writeInteger(d2+nxt*es,-1) end
    else
      local nb,mb=ri(a+0x28),ri(a+0x2C)
      if (rq(a+0x20) or 0)==0 and mb&lt;128 then mb=128 writeInteger(a+0x2C,128) end
      if nb+1&gt;mb then
        if not nbits then error('bit array is full',0) end
        local sec=rq(a+0x20)
        local src=(sec and sec~=0) and sec or (a+0x10)
        local words=(nb+31)//32
        if words&gt;0 then writeBytes(nbits.buf, readBytes(src, words*4, true)) end
        writeQword(a+0x20, nbits.buf) writeInteger(a+0x2C, nbits.max)
      end
      if n2&gt;=m2 then
        if not nd then error('array changed',0) end
        if n2&gt;0 then writeBytes(nd, readBytes(d2,n2*es,true)) end
        writeQword(a,nd) writeInteger(a+12,math.max(4,m2*2)) d2=nd
      end
      idx=n2
      writeInteger(a+8,n2+1)
      writeInteger(a+0x28,nb+1)
    end
    local sec=rq(a+0x20)
    local bits=(sec and sec~=0) and sec or (a+0x10)
    local wa=bits+(idx&gt;&gt;5)*4
    writeInteger(wa, (readInteger(wa) or 0) | (1&lt;&lt;(idx&amp;31)))
    local bi=h&amp;(hs-1)
    put(elem,lay.hn,le(ri(hb+bi*4) or -1,4)) put(elem,lay.hi,le(bi,4))
    writeBytes(d2+idx*es, elem)
    writeInteger(hb+bi*4, idx)
  end)
  return 'added'
end
function UE.dbl(v) return {string.pack('&lt;d',v):byte(1,8)} end
function UE.i32(v) return le(math.floor(v)&amp;0xFFFFFFFF,4) end

----------------------------------------------------------------- structs: POD check / clone
local PODT={IntProperty=1,Int64Property=1,UInt32Property=1,UInt64Property=1,Int16Property=1,UInt16Property=1,Int8Property=1,
            FloatProperty=1,DoubleProperty=1,BoolProperty=1,ByteProperty=1,EnumProperty=1,NameProperty=1,ObjectProperty=1,ClassProperty=1}
function UE.isPOD(p)
  if PODT[p.t] then return true end
  if p.t=='StructProperty' and p.struct then
    local c=UE.podc[p.struct]; if c~=nil then return c end
    UE.podc[p.struct]=false
    local ok=true
    for _,q in ipairs(UE.allInfo(p.struct)) do if not UE.isPOD(q) then ok=false break end end
    UE.podc[p.struct]=ok
    return ok
  end
  return false
end
-- bytes of a struct copied from src: POD copied, FString deep-copied, other containers emptied
function UE.cloneStruct(st, src, size, overrides)
  local b=readBytes(src,size,true) or zeros(size)
  for _,p in ipairs(UE.allInfo(st)) do
    local len=p.size*math.max(p.dim,1)
    local ov=overrides and overrides[p.cname]
    if p.t=='StrProperty' then put(b,p.off,UE.newFStr(ov or UE.fstr(src+p.off)))
    elseif not UE.isPOD(p) then put(b,p.off,zeros(len))
    elseif ov~=nil then
      if p.t=='IntProperty' then put(b,p.off,UE.i32(ov)) elseif p.t=='DoubleProperty' then put(b,p.off,UE.dbl(ov))
      elseif p.t=='BoolProperty' then b[p.off+p.bOff+1]= ov and p.bMask or 0 end
    end
  end
  return b
end
-- zero-initialized struct with given fields
function UE.makeStruct(st, size, vals)
  local b=zeros(size)
  for _,p in ipairs(UE.allInfo(st)) do
    local v=vals[p.cname]
    if v~=nil then
      if p.t=='StrProperty' then put(b,p.off,UE.newFStr(v))
      elseif p.t=='IntProperty' then put(b,p.off,UE.i32(v))
      elseif p.t=='DoubleProperty' then put(b,p.off,UE.dbl(v))
      elseif p.t=='FloatProperty' then put(b,p.off,{string.pack('&lt;f',v):byte(1,4)})
      elseif p.t=='BoolProperty' then b[p.off+p.bOff+1]= v and p.bMask or 0 end
    end
  end
  return b
end
-- copy UObject properties src -&gt; dst (POD raw, FString deep copy, containers skipped)
function UE.copyProps(src, dst, cls)
  for _,p in ipairs(UE.allInfo(cls)) do
    local len=p.size*math.max(p.dim,1)
    if p.t=='StrProperty' then UE.setFStr(dst+p.off, UE.fstr(src+p.off))
    elseif UE.isPOD(p) and len&gt;0 and len&lt;0x1000 then writeBytes(dst+p.off, readBytes(src+p.off,len,true)) end
  end
end

----------------------------------------------------------------- game-thread call gate (ProcessEvent hook)
function UE.gameCall(fn, a, b, c, d, timeout)
  GT.require()
  if not fn then GT.err('Function address missing.','関数アドレスがありません。') end
  GT.hookOn('peGate')
  local v=GT.var
  if (readBytes(GT.flag(0x10),1,false) or 0)~=0 then writeBytes(GT.flag(0x10),0) end
  writeQword(v(0x80),a or 0) writeQword(v(0x88),b or 0) writeQword(v(0x90),c or 0) writeQword(v(0x98),d or 0)
  writeQword(v(0xA0),fn) writeQword(v(0xA8),0)
  writeBytes(GT.flag(0x10),1)
  local t0=getTickCount()
  while true do
    local s=readBytes(GT.flag(0x10),1,false)
    if s==3 then break end
    if getTickCount()-t0&gt;(timeout or 5000) then
      if s==1 then writeBytes(GT.flag(0x10),0) end
      GT.err('The game did not run the queued call (is the game window active / not paused by the OS?).',
             'ゲームが呼び出しを実行しませんでした（ゲームが動作中か確認してください）。')
    end
    sleep(10)
  end
  local r=readQword(v(0xA8))
  writeBytes(GT.flag(0x10),0)
  return r
end
function UE.processEvent(obj, func, params) return UE.gameCall(GT.A.processEvent or GT.resolveSig('processEvent'), obj, func, params, 0) end
-- UGameplayStatics::SpawnObject(ObjectClass, Outer)
function UE.spawnObject(cls, outer)
  local gs=UE.findClass('GameplayStatics','/Script/Engine') or GT.err('GameplayStatics not found.','GameplayStaticsが見つかりません。')
  local fnc=UE.childObject('Function','SpawnObject',gs) or GT.err('SpawnObject not found.','SpawnObjectが見つかりません。')
  local cdo=UE.cdo(gs) or GT.err('GameplayStatics CDO not found.','GameplayStaticsのCDOが見つかりません。')
  local pc=UE.PS(fnc,'ObjectClass') local po=UE.PS(fnc,'Outer') local pr=UE.PS(fnc,'ReturnValue')
  if not (pc and po and pr) then GT.err('SpawnObject parameters not found.','SpawnObjectの引数が見つかりません。') end
  UE.spawnBuf=UE.spawnBuf or allocateMemory(0x100)
  local buf=UE.spawnBuf
  writeBytes(buf, zeros(0x100))
  writeQword(buf+pc.off, cls) writeQword(buf+po.off, outer)
  UE.processEvent(cdo, fnc, buf)
  local r=rq(buf+pr.off)
  if not r then GT.err('SpawnObject returned nothing.','SpawnObjectが失敗しました。') end
  return r
end

----------------------------------------------------------------- DataTable (UDataTable: RowStruct, then TMap&lt;FName,uint8*&gt; RowMap)
function UE.dataTable(name)
  local o=UE.findObject('DataTable', name) or UE.findObject('CompositeDataTable', name)
  if not o then return nil end
  local p=UE.P(o,'RowStruct'); if not p then return nil end
  local st=rq(o+p.off)
  local rm=o+p.off+8
  local d,n=UE.arr(rm)
  if n&lt;0 or n&gt;100000 then return nil end
  local sec=rq(rm+0x20)
  local bits=(sec and sec~=0) and sec or (rm+0x10)
  local rows={}
  for i=0,n-1 do
    local w=readInteger(bits+(i&gt;&gt;5)*4) or 0
    if ((w&gt;&gt;(i&amp;31))&amp;1)==1 then
      local e=d+i*24
      rows[#rows+1]={name=UE.fname(e) or ('#'..i), ptr=rq(e+8)}
    end
  end
  return {obj=o, struct=st, rows=rows}
end
function UE.dataTableNames()
  UE.scan()
  local r={}
  for _,k in ipairs({'DataTable','CompositeDataTable'}) do
    for _,e in ipairs(UE.byName[k] or {}) do
      for _,o in ipairs(e.objs) do if not UE.isCDO(o) then r[#r+1]=UE.objName(o) end end
    end
  end
  table.sort(r) return r
end
-- largest numeric value over rows (fields whose clean name matches pattern, or all numeric fields)
function UE.dtMax(name, pat)
  local dt=UE.dataTable(name); if not dt or not dt.struct then return nil end
  local best
  for _,p in ipairs(UE.allInfo(dt.struct)) do
    if (p.t=='IntProperty' or p.t=='DoubleProperty' or p.t=='FloatProperty') and (not pat or p.cname:find(pat)) then
      for _,r in ipairs(dt.rows) do
        if r.ptr then local v=UE.rd(r.ptr+p.off,p) if v and (not best or v&gt;best) then best=v end end
      end
    end
  end
  return best
end

----------------------------------------------------------------- layout detection
-- raw dump of the first FUObjectItems / UObjects (printed when the layout is not recognized)
function UE.diag(ch0)
  GT.openLuaEngine(true)
  local L=UE.L or {}
  print(('[%s] diag: objs=%s chunk0=%s num=%s max=%s'):format(GT.TAG, L.objs and hx(L.objs) or '-', ch0 and hx(ch0) or '-',
        tostring(L.objs and readInteger(L.objs+0x14)), tostring(L.objs and readInteger(L.objs+0x10))))
  for i=0,5 do
    local it=ch0 and ch0+i*0x18
    local q={} for k=0,2 do q[#q+1]=hx(it and readQword(it+k*8) or 0) end
    print(('[%s]  item%d %s'):format(GT.TAG, i, table.concat(q,' ')))
    for k=0,2 do
      local o=it and rq(it+k*8)
      if o and o&gt;0x10000 then
        local w={} for m=0,5 do w[#w+1]=hx(readQword(o+m*8) or 0) end
        local nm=UE.L and UE.L.lenShift and UE.fname(o+0x18)
        print(('[%s]    +%X -&gt; %X : %s  name=%s'):format(GT.TAG, k*8, o, table.concat(w,' '), tostring(nm)))
      end
    end
  end
end
function UE.init()
  local L={}
  local gn=GT.A.gnames or GT.resolveSig('gnames')
  local go=GT.A.gobjects or GT.resolveSig('gobjects')
  if not gn or not go then GT.err('UE globals not found (gnames / gobjects).','UEのグローバル（gnames / gobjects）が見つかりません。') end
  L.pool=gn L.objs=go L.item=0x18
  for _,off in ipairs({0x10,0x8,0x18,0x0,0x20}) do
    local b=rq(gn+off)
    if b then local s=readBytes(b+2,4,true) if s and string.char(table.unpack(s))=='None' then L.blocksOff=off L.blk0=b break end end
  end
  if not L.blocksOff then GT.err('FNamePool layout not recognized.','FNamePoolの構造を認識できません。') end
  local h=(readSmallInteger(L.blk0) or 0)&amp;0xFFFF
  if (h&gt;&gt;6)==4 then L.lenShift=6 elseif (h&gt;&gt;1)==4 then L.lenShift=1 else GT.err('FName header not recognized.','FNameヘッダーを認識できません。') end
  UE.L=L
  -- FUObjectItem: UE5.7 = {int64 FlagsAndRefCount; UObject* Object; ...} (Object +8); older UE5 = Object +0
  local ch0=rq(rq(go) or 0)
  local cnt=math.min(64, readInteger(go+0x14) or 0)
  for _,io in ipairs({0x8,0x0,0x10}) do
    local good=0
    for i=0,cnt-1 do
      local o=ch0 and rq(ch0+i*L.item+io)
      if o and readInteger(o+0xC)==i then good=good+1 end
    end
    if cnt&gt;0 and good*4&gt;=cnt*3 then L.itemObj=io break end
  end
  if not L.itemObj then UE.diag(ch0) GT.err('GUObjectArray layout not recognized (see the dump above).','GUObjectArrayの構造を認識できません（上の出力を参照）。') end
  local core
  for i=0,511 do local o=UE.objAt(i) if o and UE.objName(o)=='/Script/CoreUObject' then core=o break end end
  if not core then UE.diag(ch0) GT.err('UObject name layout not recognized (see the dump above).','UObjectの名前の構造を認識できません（上の出力を参照）。') end
  UE.scan(true)
  local cObject=UE.findObject('Class','Object','/Script/CoreUObject')
  local cActor=UE.findObject('Class','Actor','/Script/Engine')
  local sVector=UE.findObject('ScriptStruct','Vector','/Script/CoreUObject')
  if not (cObject and cActor and sVector) then GT.err('Core classes not found.','コアクラスが見つかりません。') end
  for o=0x28,0x90,8 do if rq(cActor+o)==cObject then L.super=o break end end
  for o=0x30,0xB0,8 do local f=rq(sVector+o) if f and ownerOf(f)==sVector then L.childProps=o break end end
  if not (L.super and L.childProps) then GT.err('UStruct layout not recognized.','UStructの構造を認識できません。') end
  local f0=rq(sVector+L.childProps)
  for _,c in ipairs({{0x18,0x20},{0x20,0x28},{0x18,0x28},{0x20,0x30}}) do
    local got={} local f=f0 local k=0
    while f and f~=0 and k&lt;8 do local n=UE.fname(f+c[2]) if n then got[n]=f end f=rq(f+c[1]) k=k+1 end
    if got.X and got.Y and got.Z then L.fNext=c[1] L.fName=c[2] L.fx,L.fy,L.fz=got.X,got.Y,got.Z break end
  end
  if not L.fNext then GT.err('FField layout not recognized.','FFieldの構造を認識できません。') end
  L.fClass=0x8
  for o=0x30,0x80,4 do if ri(L.fx+o)==0 and ri(L.fy+o)==8 and ri(L.fz+o)==16 then L.pOff=o break end end
  for o=0x30,0x70,4 do if ri(L.fx+o)==1 and ri(L.fx+o+4)==8 and ri(L.fy+o)==1 and ri(L.fy+o+4)==8 and ri(L.fz+o+4)==8 then L.pDim=o L.pSize=o+4 break end end
  if not (L.pOff and L.pDim) then GT.err('FProperty layout not recognized.','FPropertyの構造を認識できません。') end
  -- start of FProperty subclass data: StructProperty Actor.PrimaryActorTick -&gt; ActorTickFunction
  local tick
  for _,f in ipairs(UE.fields(cActor)) do if UE.fname(f+L.fName)=='PrimaryActorTick' then tick=f break end end
  if tick then
    for o=L.pOff+4,L.pOff+0x80,4 do local q=rq(tick+o) if q and (o%8==0) and UE.objName(q)=='ActorTickFunction' and ownerOf(q)~=tick then L.pBase=o break end end
  end
  if not L.pBase then GT.err('FProperty subclass layout not recognized.','FPropertyサブクラスの構造を認識できません。') end
  UE.L=L
  UE.ready=true
  GT.msg(('UE reflection ready: objects=%d, FField next/name=+%X/+%X, offset=+%X, size=+%X, sub=+%X.'):format(UE.scanN,L.fNext,L.fName,L.pOff,L.pSize,L.pBase),
         ('UEリフレクション準備完了: オブジェクト数=%d、FField next/name=+%X/+%X、offset=+%X、size=+%X、sub=+%X。'):format(UE.scanN,L.fNext,L.fName,L.pOff,L.pSize,L.pBase))
end
function UE.need()
  GT.require()
  if not UE.ready then UE.init() end
end


--=====================================================================================
--  [GAME] SELECTION DIALOG  (search box, multi-select check list, Back / OK / Cancel)
--=====================================================================================
GT.UI = GT.UI or {}
local UI = GT.UI
-- single: returns index | nil (cancel) | 'back'      multi: returns {indices} | nil | 'back'
local Picker = {}
local function text(v)
  return v == nil and '' or tostring(v)
end
function Picker.matches(value, query, numeric)
  query = text(query):match('^%s*(.-)%s*$')
  if query == '' then
    return true
  end
  if numeric then
    local n = tonumber(value)
    if not n then
      return false
    end
    local number = '[+-]?%d*%.?%d+'
    local lo, hi = query:match('^(' .. number .. ')%s*%-%s*(' .. number .. ')$')
    if lo then
      return n &gt;= tonumber(lo) and n &lt;= tonumber(hi)
    end
    local op, rhs = query:match('^([&lt;&gt;!=]+)%s*(' .. number .. ')$')
    rhs = tonumber(rhs)
    if rhs then
      if op == '&gt;=' then
        return n &gt;= rhs
      elseif op == '&lt;=' then
        return n &lt;= rhs
      elseif op == '&gt;' then
        return n &gt; rhs
      elseif op == '&lt;' then
        return n &lt; rhs
      elseif op == '=' or op == '==' then
        return n == rhs
      elseif op == '!=' then
        return n ~= rhs
      end
    end
    local exact = tonumber(query)
    if exact then
      return n == exact
    end
  end
  return text(value):lower():find(query:lower(), 1, true) ~= nil
end
function Picker.view(rows, columns, queries, sortIndex, ascending)
  local shown = {}
  for _, row in ipairs(rows) do
    local include = true
    for i, col in ipairs(columns) do
      if not Picker.matches(row[col.key], queries[i], col.numeric) then
        include = false
        break
      end
    end
    if include then
      shown[#shown + 1] = row
    end
  end
  local col = columns[sortIndex]
  table.sort(shown, function(a, b)
    local x, y = a[col.key], b[col.key]
    if x == nil or y == nil then
      if x ~= y then
        return y == nil
      end
    else
      if not col.numeric then
        x = text(x):lower()
        y = text(y):lower()
      end
      if x ~= y then
        if ascending then
          return x &lt; y
        else
          return x &gt; y
        end
      end
    end
    return a.id &lt; b.id
  end)
  return shown
end
function Picker.selectedRows(s, view, clicked)
  local rows, included = {}, false
  local index = -1
  while true do
    local nextIndex = tonumber(sendMessage(view.Handle, 0x100c, index, 2)) or -1
    if nextIndex &lt; 0 or nextIndex &gt;= #s.shown or nextIndex &lt;= index then
      break
    end
    local row = s.shown[nextIndex + 1]
    rows[#rows + 1] = row
    included = included or row == clicked
    index = nextIndex
  end
  if clicked and not included then
    return { clicked }
  end
  return rows
end
function Picker.create(options)
  local state = {
    rows = options.rows,
    columns = options.columns,
    shown = {},
    queries = {},
    sortIndex = options.sortIndex or 2,
    ascending = true,
    lastId = options.current,
    result = nil,
    updating = false,
  }
  local form = createForm(false)
  state.form = form
  form.Name = 'WP26_EntityPicker101'
  form.Caption = options.title
  form.Position = 'poScreenCenter'
  form.BorderStyle = 'bsSizeable'
  form.ClientWidth = options.browse and 1500 or 840
  form.ClientHeight = options.browse and 820 or math.max(330, math.min(620, 180 + #options.rows * 32))
  form.Font.Name = 'Yu Gothic UI'
  form.Font.Size = 10
  form.Color = 0x252525
  form.Font.Color = 0xf0f0f0
  pcall(function()
    form.Constraints.MinWidth = options.browse and 900 or 700
    form.Constraints.MinHeight = 380
  end)
  local list = createListView(form)
  state.list = list
  list.Name = 'WP26_EntityTable101'
  list.ViewStyle = 'vsReport'
  list.RowSelect = true
  list.HideSelection = false
  list.ReadOnly = true
  list.MultiSelect = options.multi == true
  list.OwnerData = true
  list.Color = 0x252525
  list.Font.Color = 0xf0f0f0
  local headers = {}
  list.ShowColumnHeaders = true
  for i, col in ipairs(state.columns) do
    local h = list.Columns.add()
    h.Caption = col.title
    headers[i] = h
  end
  local field = createComboBox(form)
  field.Style = 'csDropDownList'
  field.Color = 0x353535
  field.Font.Color = 0xffffff
  field.Items.add(GT.T('All columns','すべての列'))
  for _, col in ipairs(state.columns) do field.Items.add(col.title) end
  field.ItemIndex = 0
  local search = createEdit(form)
  search.Color = 0x353535
  search.Font.Color = 0xffffff
  local count = createLabel(form)
  state.count = count
  count.Font.Color = 0xcfcfcf
  local clear = createButton(form)
  state.clear = clear
  clear.Caption = GT.T('Clear filters', '検索解除')
  local accept = createButton(form)
  state.accept = accept
  accept.Name = 'WP26_EntityAccept101'
  accept.Caption = GT.T('Select', '選択')
  accept.Default = true
  local cancel = createButton(form)
  state.cancel = cancel
  cancel.Name = 'WP26_EntityCancel101'
  cancel.Caption = GT.T('Cancel', 'キャンセル')
  cancel.Cancel = true
  function state.selectedId()
    local item = list.Selected
    local row = item and state.shown[item.Index + 1]
    return row and row.id or nil
  end
  list.OnData = function(sender, item)
    local row = state.shown[item.Index + 1]
    if not row then
      return
    end
    item.Caption = text(row[state.columns[1].key])
    item.SubItems.clear()
    for i = 2, #state.columns do
      item.SubItems.add(text(row[state.columns[i].key]))
    end
  end
  local initialSelection = options.checked
  function state.render()
    if state.updating then
      return
    end
    local selectedIds = {}
    for _, id in ipairs(initialSelection or {}) do selectedIds[id] = true end
    initialSelection = nil
    if options.multi then
      for _, row in ipairs(Picker.selectedRows(state, list)) do selectedIds[row.id] = true end
    end
    local selected = state.selectedId()
    if selected ~= nil then
      state.lastId = selected
    end
    local rows = state.rows
    local query = state.queries[0]
    if query and query:match('%S') then
      rows = {}
      for _, row in ipairs(state.rows) do
        for _, col in ipairs(state.columns) do
          if Picker.matches(row[col.key], query, false) then
            rows[#rows+1] = row
            break
          end
        end
      end
    end
    state.shown = Picker.view(rows, state.columns, state.queries, state.sortIndex, state.ascending)
    list.beginUpdate()
    list.Items.clear()
    list.Items.Count = #state.shown
    local selectedIndex = -1
    for i, row in ipairs(state.shown) do
      if row.id == state.lastId then
        selectedIndex = i - 1
        break
      end
    end
    if options.multi then
      for i,row in ipairs(state.shown) do
        if selectedIds[row.id] then list.Items[i-1].Selected = true end
      end
    else
      list.ItemIndex = selectedIndex
    end
    list.endUpdate()
    if not options.multi and state.lastId == nil and #state.shown &gt; 0 then
      list.ItemIndex = 0
      state.lastId = state.shown[1].id
    end
    for i, h in ipairs(headers) do
      h.Caption = state.columns[i].title .. (i == state.sortIndex and (state.ascending and ' ↑' or ' ↓') or '')
    end
    count.Caption = GT.T('Shown ', '表示 ') .. #state.shown .. ' / ' .. #state.rows
    accept.Enabled = options.multi and #Picker.selectedRows(state,list) &gt; 0 or state.selectedId() ~= nil
  end
  function state.sort(index)
    if state.sortIndex == index then
      state.ascending = not state.ascending
    else
      state.sortIndex = index
      state.ascending = true
    end
    state.render()
  end
  list.OnColumnClick = function(sender, column)
    for i, h in ipairs(headers) do
      if h.Caption == column.Caption then
        state.sort(i)
        return
      end
    end
  end
  list.OnSelectItem = function()
    accept.Enabled = options.multi and #Picker.selectedRows(state,list) &gt; 0 or state.selectedId() ~= nil
  end
  local function choose()
    if options.multi then
      local ids = {}
      for _, row in ipairs(Picker.selectedRows(state,list)) do ids[#ids+1] = row.id end
      if #ids &gt; 0 then
        state.result = ids
        form.ModalResult = 1
      end
      return
    end
    local id = state.selectedId()
    if id ~= nil then
      state.result = id
      form.ModalResult = 1
    end
  end
  accept.OnClick = choose
  list.OnDblClick = choose
  cancel.OnClick = function()
    state.result = nil
    form.ModalResult = 2
  end
  search.OnChange = function()
    if state.updating then return end
    state.queries[field.ItemIndex] = search.Text
    state.render()
  end
  field.OnChange = function()
    state.updating = true
    search.Text = state.queries[field.ItemIndex] or ''
    state.updating = false
  end
  clear.OnClick = function()
    state.queries = {}
    state.updating = true
    search.Text = ''
    state.updating = false
    state.render()
  end
  list.OnCustomDrawItem = function(sender, item, drawState)
    local row = state.shown[item.Index + 1]
    if not row then
      return false
    end
    local rect = item.displayRect('drBounds')
    local canvas = sender.Canvas
    canvas.Font.Color = 0xffffff
    canvas.Brush.Color = item.Selected and 0x805020 or 0x252525
    canvas.fillRect(rect.Left, rect.Top, sender.Width, rect.Bottom)
    local x = rect.Left
    for i, col in ipairs(state.columns) do
      local width = headers[i].Width
      canvas.textRect(
        { Left = x + 6, Top = rect.Top, Right = x + width - 6, Bottom = rect.Bottom },
        x + 6,
        rect.Top + 2,
        text(row[col.key])
      )
      x = x + width
    end
    return false
  end
  local back
  if options.back then
    back = createButton(form)
    back.Caption = GT.T('Back','戻る')
    back.OnClick = function()
      state.result = 'back'
      form.ModalResult = 2
    end
  end
  if options.browse then
    list.OnCustomDrawItem = nil
    accept.Visible = false
    list.OnDblClick = nil
    cancel.Caption = GT.T('Close','閉じる')
    local refresh = createButton(form)
    refresh.Caption = GT.T('Refresh','更新')
    state.refresh = refresh
  end
  local function layout()
    local w, h = form.ClientWidth, form.ClientHeight
    field.Left = 14
    field.Top = 12
    field.Width = 264
    search.Left = 284
    search.Top = 12
    search.Width = options.browse and w - 680 or w - 298
    local available = w - 48
    local fixed, flex = 0, 0
    for _, col in ipairs(state.columns) do
      if col.flex then
        flex = flex + 1
      else
        fixed = fixed + (col.width or 100)
      end
    end
    for i, col in ipairs(state.columns) do
      local width = col.flex and math.max(180, math.floor((available - fixed) / math.max(1, flex)))
        or (col.width or 100)
      headers[i].Width = width
      headers[i].MinWidth = width
      headers[i].MaxWidth = width
    end
    list.Left = 14
    list.Top = options.browse and 80 or 58
    list.Width = w - 28
    list.Height = h - list.Top - 64
    count.Left = 16
    count.Top = h - 43
    clear.Left = 155
    clear.Top = h - 49
    clear.Width = 110
    clear.Height = 33
    accept.Left = w - 266
    accept.Top = h - 49
    accept.Width = 118
    accept.Height = 33
    cancel.Left = w - 136
    cancel.Top = h - 49
    cancel.Width = 118
    cancel.Height = 33
    if options.browse then
      clear.Left = w - 380
      clear.Top = 10
      clear.Height = 32
      state.refresh.Left = w - 260
      state.refresh.Top = 10
      state.refresh.Width = 110
      state.refresh.Height = 32
      cancel.Left = w - 140
      cancel.Top = 10
      cancel.Width = 126
      cancel.Height = 32
      count.Top = 52
    end
    if back then
      back.Left = w - 396
      back.Top = h - 49
      back.Width = 118
      back.Height = 33
    end
  end
  form.OnResize = layout
  form.OnShow = state.render
  layout()
  state.render()
  function state.destroy()
    form.destroy()
  end
  return state
end
UI.worldLists = UI.worldLists or {}
function UI.browse(kind, title, columns, readRows, actions)
  local old = UI.worldLists[kind]
  if old then old.form.show(); old.form.bringToFront(); return end
  local state = Picker.create({title=title, columns=columns, rows=readRows(), sortIndex=2, browse=true, multi=true})
  UI.worldLists[kind] = state
  state.form.OnClose = function()
    UI.worldLists[kind] = nil
    return caFree
  end
  local function refresh()
    state.rows = readRows()
    state.render()
  end
  state.refresh.OnClick = function() GT.userRun(refresh) end
  local list = state.list
  local popup = createPopupMenu(state.form)
  local targets
  list.PopupMenu = popup
  list.OnMouseDown = function(sender, button, x, y)
    if button ~= 'mbRight' and button ~= 1 then return end
    local item = sender.getItemAt(x,y)
    local row = item and state.shown[item.Index+1]
    targets = row and Picker.selectedRows(state,list,row) or nil
    for i, action in ipairs(actions) do
      popup.Items[i-1].Enabled = targets ~= nil and (not action.single or #targets==1)
    end
  end
  for _, action in ipairs(actions) do
    local entry = createMenuItem(popup)
    entry.Caption = action.title
    entry.Enabled = false
    entry.OnClick = function()
      if not targets or (action.single and #targets~=1) then return end
      GT.userRun(function()
        action.run(targets)
        if not action.single then refresh() end
      end)
    end
    popup.Items.add(entry)
  end
  list.OnDblClick = function()
    local x,y = getMousePos()
    x,y = list.screenToClient(x,y)
    local item = list.getItemAt(x,y)
    local row = item and state.shown[item.Index+1]
    if row then GT.userRun(function() actions[1].run({row}) end) end
  end
  state.form.show()
end

function Picker.show(options)
  local state = Picker.create(options)
  local ok, result = pcall(function()
    state.form.showModal()
    return state.result
  end)
  state.destroy()
  if not ok then
    error(result, 0)
  end
  return result
end
function UI.select(title, names, o)
  o = o or {}
  local columns, rows = {}, {}
  local titles = o.columns or {GT.T('Name','名前')}
  for i, label in ipairs(titles) do
    columns[i] = {key=i, title=label, numeric=i==o.numericColumn,
      width=i==o.numericColumn and 100 or nil, flex=i~=o.numericColumn}
  end
  for i, name in ipairs(names) do
    local row = {id=i, [1]=name}
    if #columns &gt; 1 then
      local index = 1
      for value in (name .. '|'):gmatch('(.-)|') do
        row[index] = value:match('^%s*(.-)%s*$')
        index = index + 1
      end
    end
    if o.numericColumn then row[o.numericColumn] = tonumber(row[o.numericColumn]) end
    rows[i] = row
  end
  return Picker.show({title=title, rows=rows, columns=columns, sortIndex=1, multi=o.multi, checked=o.checked, back=o.back})
end
-- integer input; returns number or nil (cancel)
function UI.askInt(title, prompt, def, lo, hi)
  while true do
    local s=inputQuery(title, prompt, tostring(def or 0))
    if not s then return nil end
    local v=tonumber(s)
    if v and v%1==0 and (not lo or v&gt;=lo) and (not hi or v&lt;=hi) then return v end
    showMessage(GT.T('Please enter a whole number in the allowed range.','範囲内の整数を入力してください。'))
  end
end


--=====================================================================================
--  [GAME] GAME LAYER  (Blueprint data classes of The End of History)
--=====================================================================================
GT.G = GT.G or {}
local G = GT.G
local UE = GT.UE
local rq, ri = GT.rq, GT.ri
local T = GT.T
local UI = GT.UI
local function hx(n) return string.format('%X', n) end

G.C = {
  unit='UnitDataClass_C', army='ArmyDataClass_C', spot='SpotDataClass_C', power='PowerDataClass_C',
  country='CountryDataClass_C', soldier='SoldierDataClass_C', gm='MyGameMode_C', gi='MyGameInstance_C',
  pactor='MyBP_PlayerUnitActor_C', dilemma='DilemmaManager_C', discovery='DiscoverySpotActor2_C',
}
G.ABIL = { {'Leadership','Leadership','統率'}, {'Politics','Politics','政治'}, {'Inteligence','Intelligence','知力'},
           {'Battle','Battle','武力'}, {'Intuition','Intuition','直感'}, {'Charm','Charm','魅力'} }
G.DISCLOSE = {'Battle','Leadership','Politics','Charm','Inteligence','Char0','Char1','Char2','Intuition'}

function G.reset() G.nrm=nil G.cache={} G.maps={} G.sel={} G.st={} G.neg={} math.randomseed(os.time()+getTickCount()) end
if not G.cache then G.reset() end

----------------------------------------------------------------- lookups
function G.valid(o, cname)
  if not o or o==0 then return false end
  local c=rq(o+0x10); if not c then return false end
  if UE.isCDO(o) then return false end
  return UE.objName(c)==cname
end
-- refresh object list for a user action (one full scan)
function G.fresh() UE.need() UE.scan(true) end
function G.map(kind)
  local cname=G.C[kind]
  UE.scan()
  local m=G.maps[kind]
  if m and m.t==UE.scanT then return m end
  m={t=UE.scanT, list={}, byId={}}
  -- authoritative lists of the current world are held by the game mode (AllUnitDataClass etc.);
  -- objects of earlier loads that are waiting for garbage collection are therefore ignored
  local src
  local arr=G.GMLIST[kind]
  if arr then
    local best,bn
    for _,gm in ipairs(UE.instances(G.C.gm)) do
      local a=UE.addr(gm,arr)
      if a then local d,n=UE.arr(a) if d~=0 and n&gt;0 and (not bn or n&gt;bn) then best,bn=a,n end end
    end
    if best then
      src={} local d,n=UE.arr(best)
      for i=0,n-1 do local o=rq(d+i*8) if o and G.valid(o,cname) then src[#src+1]=o end end
    end
  end
  if not src then src=UE.instances(cname) end
  for _,o in ipairs(src) do
    local id=UE.get(o,G.IDNAMES)
    if id and id~='' then
      if not m.byId[id] then m.byId[id]=o m.list[#m.list+1]=o end   -- one object per ID
    else m.list[#m.list+1]=o end
  end
  G.maps[kind]=m
  return m
end
G.GMLIST={unit='AllUnitDataClass', spot='AllSpotDataClass', power='AllPowerDataClass', country='AllCountryDataClass', army='AllArmyDataClass'}
function G.byId(kind, id) if not id or id=='' then return nil end return G.map(kind).byId[id] end
G.IDNAMES='ID|UnitID|SpotID|PowerID|CountryID'
function G.id(o) return (UE.get(o,G.IDNAMES)) or '' end
-- cached object lookup; when the cache is stale a full rescan is done at most every 20 s (never for 'noscan')
local function cached(key, cname, finder, mode)
  local o=G.cache[key]
  if o and G.valid(o,cname) then return o end
  G.cache[key]=nil
  if not UE.ready then return nil end
  local t=G.neg[key]
  if t and getTickCount()-t&lt;2000 then return nil end
  local ok,r=pcall(finder)
  if (not ok or not r) and mode~='noscan' and getTickCount()-UE.scanT&gt;20000 then
    pcall(UE.scan,true) ok,r=pcall(finder)
  end
  if ok and r then G.cache[key]=r G.neg[key]=nil return r end
  G.neg[key]=getTickCount()
  return nil
end
function G.player(mode)
  local o=G.cache.player
  if o and G.valid(o,G.C.unit) then return o end
  G.cache.player=nil
  return cached('player', G.C.unit, function()
    for _,u in ipairs(G.map('unit').list) do if UE.get(u,'IsPlayer') then return u end end
    local gm=UE.instances(G.C.gm)[1]
    local pid=gm and UE.get(gm,'PlayerUnitID')
    if pid and pid~='' then return G.byId('unit',pid) end
  end, mode)
end
function G.playerPower(mode)
  return cached('power', G.C.power, function()
    local pid=G.id(G.player(mode) or 0)
    if pid=='' then return nil end
    local list=G.map('power').list
    for _,p in ipairs(list) do if UE.get(p,'LeaderUnitID')==pid and not UE.get(p,'IsPerished') then return p end end
    for _,p in ipairs(list) do if UE.strArrHas(p,'MemberUnitIDs',pid) then return p end end
  end, mode)
end
function G.playerCountry(mode)
  return cached('country', G.C.country, function()
    local pw=G.playerPower(mode); if not pw then return nil end
    local wid=G.id(pw)
    local list=G.map('country').list
    for _,c in ipairs(list) do if UE.get(c,'LeaderPowerId')==wid and not UE.get(c,'IsPerished') then return c end end
    for _,c in ipairs(list) do if UE.strArrHas(c,'MemberPowerIDs',wid) then return c end end
  end, mode)
end
function G.playerArmy(mode)
  local a=G.cache.army
  if a then
    local u=G.player(mode)
    local aid=u and UE.get(u,'OwningArmyID')
    local okA=u and G.valid(a,G.C.army) and ((aid and aid~='' and G.id(a)==aid) or ((not aid or aid=='') and UE.get(a,'LeaderID')==G.id(u)))
    if not okA then G.cache.army=nil end
  end
  return cached('army', G.C.army, function()
    local u=G.player(mode); if not u then return nil end
    local pid=G.id(u)
    local a=G.byId('army', UE.get(u,'OwningArmyID'))
    if a then return a end
    for _,x in ipairs(G.map('army').list) do if UE.get(x,'LeaderID')==pid then return x end end
  end, mode)
end
function G.timeOwner(mode)
  return cached('time', G.C.gm, function()
    for _,o in ipairs(UE.instances(G.C.gm)) do if UE.P(o,'TimeYear') then return o end end
  end, mode) or cached('time2', G.C.gi, function()
    for _,o in ipairs(UE.instances(G.C.gi)) do if UE.P(o,'TimeYear') then return o end end
  end, mode)
end
function G.selValid(kind)
  local s=G.sel[kind]; if not s then return nil end
  local c=rq(s.o+0x10)
  if c~=s.cls or UE.isCDO(s.o) then G.sel[kind]=nil return nil end
  return s.o
end
function G.select(kind, o) G.sel[kind]={o=o, cls=rq(o+0x10)} end
function G.need(o, en, ja) if not o then GT.err(en, ja) end return o end
function G.needPlayer() return G.need(G.player(),'Player data not found (load a save first).','プレイヤーデータが見つかりません（セーブデータをロードしてください）。') end
function G.needPower() return G.need(G.playerPower(),'Your faction was not found.','自勢力が見つかりません。') end
function G.needArmy() return G.need(G.playerArmy(),'Your army was not found.','自部隊が見つかりません。') end
function G.needCountry() return G.need(G.playerCountry(),'Your country was not found (you may not have founded one).','自国が見つかりません（建国前の可能性があります）。') end

----------------------------------------------------------------- display names
local function L2(t) if not t then return nil end return GT.lang=='ja' and t[1] or t[2] end
-- FirstNameID / FamilyNameID are StringIDs of DT_FirstNameList / DT_FamilyNameList (row name = StringID)
function G.nameRowMap(dtName)
  G.nrm=G.nrm or {}
  local m=G.nrm[dtName]
  if m then return m end
  m={}
  local ok,dt=pcall(UE.dataTable,dtName)
  if ok and dt and dt.struct then
    local pid=UE.PS(dt.struct,'StringID')
    if pid then for _,r in ipairs(dt.rows) do if r.ptr then local sid=UE.fstr(r.ptr+pid.off) if sid~='' then m[sid]=r.name end end end end
  end
  G.nrm[dtName]=m
  return m
end
function G.nameText(id, dtName, tbl)
  if not id or id=='' then return '' end
  local row=G.nameRowMap(dtName)[id] or id
  local t=tbl[row] or tbl[id]
  return t and L2(t) or id
end
function G.unitName(u)
  if not u then return '?' end
  local f=G.nameText(UE.get(u,'FirstNameID|FirstName'),'DT_FirstNameList',GT.FIRSTNAMES)
  local fa=G.nameText(UE.get(u,'FamilyNameID|FamilyName'),'DT_FamilyNameList',GT.FAMILYNAMES)
  local n=(fa~='' and (f..(GT.lang=='ja' and '・' or ' ')..fa)) or f
  if n=='' then n='#'..G.id(u) end
  return n
end
function G.spotName(id)
  local i=tonumber(id)
  local s=i and GT.SPOTS[i+1]
  return s and L2(s) or ('Spot#'..tostring(id))
end
function G.powerName(p)
  if not p then return T('(none)','（なし）') end
  local l=G.byId('unit', UE.get(p,'LeaderUnitID'))
  return l and T(G.unitName(l)..' faction', G.unitName(l)..'勢力') or ('#'..G.id(p))
end
local function lookup(list, id) for _,x in ipairs(list) do if x[1]==id then return L2(x) end end return id end
function G.itemName(id) for _,x in ipairs(GT.ITEMS) do if x[1]==id then return L2(x) end end return id end
function G.goodsName(id) return lookup(GT.GOODS, id) end
function G.soldierTypeName(id) return lookup(GT.SOLDIERTYPES, id) end
function G.facilityName(id) return lookup(GT.FACILITIES, id) end
-- spot ID -&gt; owning power object
function G.spotOwners()
  local r={}
  for _,p in ipairs(G.map('power').list) do
    if not UE.get(p,'IsPerished') then for _,sid in ipairs(UE.strArr(p,'MemberSpotIDs') or {}) do r[sid]=p end end
  end
  return r
end
function G.unitPowers()
  local r={}
  for _,p in ipairs(G.map('power').list) do
    if not UE.get(p,'IsPerished') then
      local l=UE.get(p,'LeaderUnitID') if l and l~='' then r[l]=p end
      for _,uid in ipairs(UE.strArr(p,'MemberUnitIDs') or {}) do r[uid]=r[uid] or p end
    end
  end
  return r
end

----------------------------------------------------------------- pickers
G.SPOTTYPE={City={'City','都市'},Village={'Village','村'},Fort={'Fort','砦'}}
-- persons; o.multi -&gt; list of units, single -&gt; unit; nil = cancel, 'back' = back
function G.pickUnits(title, filter, o)
  o=o or {}
  local owners=G.unitPowers()
  local my=G.playerPower() and G.id(G.playerPower())
  local list={}
  for _,u in ipairs(G.map('unit').list) do
    if not UE.get(u,'dv_IsDead') and (not filter or filter(u, owners[G.id(u)])) then list[#list+1]=u end
  end
  if #list==0 then GT.err('No matching person.','該当する人物がいません。') end
  table.sort(list, function(a,b) return G.unitName(a)&lt;G.unitName(b) end)
  local names={}
  for i,u in ipairs(list) do
    local ow=owners[G.id(u)]
    local mark=(ow and my and G.id(ow)==my) and T(' (yours)','（自勢力）') or ''
    names[i]=('%s   |  %s%s  |  %s'):format(G.unitName(u), G.powerName(ow), mark, tostring(UE.get(u,'dv_Age') or '?'))
  end
  o.columns={T('Person','人物'),T('Faction','勢力'),T('Age','年齢')}
  o.numericColumn=3
  local r=UI.select(title, names, o)
  if r==nil or r=='back' then return r end
  if o.multi then local t={} for _,k in ipairs(r) do t[#t+1]=list[k] end return t end
  return list[r]
end
function G.pickUnit(title, filter) return G.pickUnits(title, filter, {}) end
function G.pickSpots(title, filter, o)
  o=o or {}
  local owners=G.spotOwners()
  local list={}
  for _,s in ipairs(G.map('spot').list) do if not filter or filter(s, owners[G.id(s)]) then list[#list+1]=s end end
  if #list==0 then GT.err('No matching city.','該当する都市がありません。') end
  table.sort(list, function(a,b) return (tonumber(G.id(a)) or 999)&lt;(tonumber(G.id(b)) or 999) end)
  local names={}
  for i,s in ipairs(list) do
    local id=tonumber(G.id(s)) local st=id and GT.SPOTS[id+1] and G.SPOTTYPE[GT.SPOTS[id+1][3]]
    names[i]=('%s   |  %s  |  %s'):format(G.spotName(G.id(s)), st and L2(st) or '?', G.powerName(owners[G.id(s)]))
  end
  o.columns={T('City','都市'),T('Type','種類'),T('Faction','勢力')}
  local r=UI.select(title, names, o)
  if r==nil or r=='back' then return r end
  if o.multi then local t={} for _,k in ipairs(r) do t[#t+1]=list[k] end return t end
  return list[r]
end
function G.pickSpot(title, filter) return G.pickSpots(title, filter, {}) end
function G.ownSpots()
  local pw=G.needPower()
  local r={}
  for _,sid in ipairs(UE.strArr(pw,'MemberSpotIDs') or {}) do local s=G.byId('spot',sid) if s then r[#r+1]=s end end
  return r
end

----------------------------------------------------------------- rows (dynamic value records)
function GT.addVal(parent, en, ja, addr, p)
  local vt
  local t=p and p.t
  if t=='IntProperty' or t=='EnumProperty' then vt=vtDword elseif t=='DoubleProperty' then vt=vtDouble
  elseif t=='FloatProperty' then vt=vtSingle elseif t=='BoolProperty' or t=='ByteProperty' then vt=vtByte
  elseif t=='Int64Property' then vt=vtQword else return nil end
  local m=GT.addRec(parent, en, ja, addr, vt)
  if vt==vtDword then pcall(function() m.ShowAsSigned=true end) end
  return m
end
-- rows for named properties of the object in pointer slot &lt;slot&gt;
function G.rows(mr, slot, obj, fields)
  local n=0
  for _,f in ipairs(fields) do
    local p=UE.P(obj, f[1])
    if p then
      local a=('[gtPtrs+%X]+%X'):format(slot*8, p.off)
      if p.t=='BoolProperty' and p.bOff&gt;0 then a=a..'+'..string.format('%X',p.bOff) end
      if GT.addVal(mr, f[2] or f[1], f[3] or f[2] or f[1], a, p) then n=n+1 end
    end
  end
  return n
end

-- rows with absolute addresses (UObjects do not move while they exist)
function G.rowsAbs(mr, obj, fields)
  for _,f in ipairs(fields) do
    local p=UE.P(obj, f[1])
    if p then
      local a=obj+p.off+((p.t=='BoolProperty' and p.bOff) or 0)
      GT.addVal(mr, f[2] or f[1], f[3] or f[2] or f[1], ('%X'):format(a), p)
    end
  end
end

----------------------------------------------------------------- 1. player
-- realtime battle: the player's battle pawn is the controlled pawn of class BattleActor_Player_C
--   (GameInstance.LocalPlayers[0].PlayerController.Pawn, confirmed in battle).
--   Its values are in AIStatList.Stats (S_Stat {Tag:GameplayTag, Value, Min, Max}); confirmed tags:
--   Stat.Health (HP, Value/Max), Stat.MovementSpeed / Stat.SprintSpeed / Stat.StrafeSpeed (speed, Value).
function G.statSlots(o)
  local cls=rq(o+0x10)
  G.st.statC=G.st.statC or {}
  local c=G.st.statC[o]
  -- a cached layout is reused only when it was complete (the Stats array of a just-spawned actor can still be empty)
  if c and c.cls==cls and c.ok then return c end
  if c and c.cls==cls and c.t and getTickCount()-c.t&lt;300 then return c end
  c={cls=cls, byTag={}, t=getTickCount()}
  G.st.statC[o]=c
  local sp=UE.P(o,'AIStatList'); if not sp or not sp.struct then return c end
  local ap=UE.PS(sp.struct,'Stats'); if not ap or not ap.inner or not ap.inner.struct then return c end
  local es=ap.inner.struct
  local pt,pv,pmx=UE.PS(es,'Tag'),UE.PS(es,'Value'),UE.PS(es,'Max')
  if not pt or not pv or not pmx then return c end
  local pn=pt.struct and UE.PS(pt.struct,'TagName')
  c.arr=o+sp.off+ap.off c.esz=ap.inner.size c.tagOff=pt.off+(pn and pn.off or 0) c.pv=pv c.pmx=pmx
  local d,n=UE.arr(c.arr)
  for i=0,math.min(n,64)-1 do local tn=UE.fname(d+i*c.esz+c.tagOff) if tn then c.byTag[tn]=i end end
  c.ok=c.byTag['Stat.Health']~=nil
  return c
end
-- address of the Value of stat &lt;tag&gt; (and its Max)
function G.statAddr(o, tag)
  local c=G.statSlots(o); if not c.arr then return nil end
  local i=c.byTag[tag]; if not i then return nil end
  local d,n=UE.arr(c.arr); if i&gt;=n then c.ok=false return nil end
  local e=d+i*c.esz
  -- the element must still carry this tag (the array can be rebuilt by the game)
  if UE.fname(e+c.tagOff)~=tag then c.ok=false c.t=nil return nil end
  return e+c.pv.off, c.pv, e+c.pmx.off, c.pmx
end
-- HP of a battle pawn.  Bytecode of BP_AI_Main_C.HandleAIDamageTaken (TheEndOfHistory_DamageCode.txt):
--   it reads AfterDamageInvincible first, then RepCurrentHealth (Vector2D: X current, Y max) is broken up,
--   FinalDamage is subtracted from X, RepCurrentHealth is written back, the Stat value is set from it
--   (SetCurrentStatValue) and death is decided by LessEqual on RepCurrentHealth.X.
--   =&gt; RepCurrentHealth.X is the HP the game uses; AIStatList Stat.Health is a copy written from it.
function G.repHealth(o)
  local p=UE.P(o,'RepCurrentHealth'); if not p then return nil end
  return o+p.off, readDouble(o+p.off), readDouble(o+p.off+8)
end
-- keep a battle pawn alive: AfterDamageInvincible on (checked first by HandleAIDamageTaken), HP to max.
-- A pawn whose HP already reached 0 is not touched (death has been decided by the game).
function G.pawnFull(o)
  local a,x,y=G.repHealth(o)
  if not a or not x or not y or y&lt;=0 or y&gt;1e7 then return end
  if x&lt;=0 then return end
  local ip=UE.P(o,'AfterDamageInvincible') if ip and not UE.rd(o+ip.off,ip) then UE.wr(o+ip.off,ip,true) end
  if x&lt;y then writeDouble(a, y) end
  G.statFull(o,'Stat.Health')
end
function G.statFull(o, tag)
  local a,p,am,pm=G.statAddr(o, tag); if not a then return end
  local v,mx=UE.rd(a,p), UE.rd(am,pm)
  if tag=='Stat.Health' and (not v or v&lt;=0) then return end
  if v and mx and mx&gt;0 and v&lt;mx then UE.wr(a,p,mx) end
end
-- realtime battle actors, found independently of what the player controls
--   A battle is going on while the player unit's IsInBattle is true (confirmed in the battle diagnosis) or the controlled
--   pawn is a battle pawn / the battle camera (log of 14:36: in some battles the controlled pawn is SpotCameraActor_C).
--   All objects of classes BattleActor_* are classified once per scan (scans at battle start, after 3 s and 10 s,
--   then every 30 s for reinforcements):
--     player : BattleActor_Player_C, or UnitDataClass / OwningUnitDataClassRef = player unit
--     own    : IsPlayerSide = true and BelongingArmyDataClass = your army
--     enemy  : IsPlayerSide = false
--   Every write is preceded by UE.isLive (destroyed actors are never written).
local function liveActor(x) return UE.isLive(x) and not UE.get(x,'bActorIsBeingDestroyed') end
-- side checks repeated at every write: a destroyed actor's address can be reused by a new actor (e.g. an enemy)
local function isPlayerActor(x)
  if UE.className(x)=='BattleActor_Player_C' then return true end
  local u=G.player('noscan') if not u then return false end
  return UE.get(x,'UnitDataClass')==u or UE.get(x,'OwningUnitDataClassRef')==u
end
-- own   : IsPlayerSide = true AND BelongingArmyDataClass = your army (same rule at scan time and at every write)
-- enemy : IsPlayerSide = false AND not in your army
-- your army objects: G.playerArmy() and the BelongingArmyDataClass of your battle pawn
local function ownArmies()
  local st=G.st
  local now=getTickCount()
  if st.oa and now-st.oa.t&lt;500 then return st.oa.set end
  local set={}
  local a=G.playerArmy('noscan') if a then set[a]=true end
  local pp=st.bat and st.bat.pp
  if pp and UE.isLive(pp) then local b=UE.get(pp,'BelongingArmyDataClass') if b and b~=0 then set[b]=true end end
  st.oa={t=now, set=set}
  return set
end
local function isOwnActor(x)
  if not liveActor(x) or isPlayerActor(x) then return false end
  if UE.get(x,'IsPlayerSide')~=true then return false end
  local b=UE.get(x,'BelongingArmyDataClass')
  return b~=nil and b~=0 and ownArmies()[b]==true
end
local function isEnemyActor(x)
  if not liveActor(x) or isPlayerActor(x) then return false end
  if UE.get(x,'IsPlayerSide')~=false then return false end
  local b=UE.get(x,'BelongingArmyDataClass')
  return not (b and ownArmies()[b])
end
G.isPlayerActor,G.isOwnActor,G.isEnemyActor=isPlayerActor,isOwnActor,isEnemyActor
function G.inRealtimeBattle()
  local u=G.player('noscan')
  if u and UE.get(u,'IsInBattle') then return true end
  local pw=G.curPawn()
  local cn=pw and UE.className(pw)
  if cn and (cn:sub(1,12)=='BattleActor_' or cn=='BattleCameraActor_C') then return true end
  return false
end
function G.battleActors()
  local st=G.st
  local now=getTickCount()
  if st.batR and now-st.batR.t&lt;100 then return st.batR end
  if not G.inRealtimeBattle() then st.bat=nil st.batR=nil return {own={}, enemies={}} end
  local b=st.bat
  if not b then b={t0=now, nextFull=now, nextLite=now, pl={}, own={}, en={}} st.bat=b end
  local u=G.player('noscan')
  if now&gt;=b.nextFull then
    local age=now-b.t0
    b.nextFull=now+((age&lt;1000 and 1000) or (age&lt;3000 and 2000) or 20000)
    pcall(UE.scan,true)
    local pl,own,en={}, {}, {}
    b.ba={} b.walls={}
    for _,x in ipairs(UE.instances('BattleActor_C')) do if liveActor(x) then b.ba[#b.ba+1]=x end end
    for cn,ents in pairs(UE.byName or {}) do if cn:find('CityWall',1,true) then for _,e in ipairs(ents) do for _,x in ipairs(e.objs) do if not UE.isCDO(x) and liveActor(x) then b.walls[#b.walls+1]=x end end end end end
    for cn,ents in pairs(UE.byName or {}) do
      if cn:sub(1,12)=='BattleActor_' then
        for _,e in ipairs(ents) do for _,x in ipairs(e.objs) do
          if not UE.isCDO(x) and UE.P(x,'AIStatList') and liveActor(x) then
            if isPlayerActor(x) then pl[#pl+1]=x
            elseif UE.get(x,'IsPlayerSide')==true then own[#own+1]=x     -- side is re-checked (army) below
            elseif UE.get(x,'IsPlayerSide')==false then en[#en+1]=x end
          end
        end end
      end
    end
    b.pl,b.own,b.en=pl,own,en
    b.nextLite=0
  end
  -- player pawn: the controlled one when it is a battle pawn, else the first live candidate
  local cp=G.curPawn()
  local pp=(cp and UE.className(cp)=='BattleActor_Player_C') and cp or nil
  if not pp then for _,x in ipairs(b.pl) do if liveActor(x) and isPlayerActor(x) then pp=x break end end end
  b.pp=pp
  -- light refresh: your army's battle pawns from the player pawn's list (no object scan)
  if now&gt;=b.nextLite and pp then
    b.nextLite=now+2000
    local seen={} for _,x in ipairs(b.own) do seen[x]=true end
    local la=UE.addr(pp,'OwnArmyMemberPawnsCanInvalidOnDead')
    if la then local d,n=UE.arr(la) for i=0,math.min(n,2000)-1 do local x=rq(d+i*8) if x and x~=0 and not seen[x] and liveActor(x) and UE.P(x,'AIStatList') then seen[x]=true b.own[#b.own+1]=x end end end
  end
  local r={t=now, player=pp, own={}, enemies={}}
  for _,x in ipairs(b.own) do if isOwnActor(x) then r.own[#r.own+1]=x end end
  for _,x in ipairs(b.en) do if isEnemyActor(x) then r.enemies[#r.enemies+1]=x end end
  st.batR=r
  return r
end
function G.battlePawn() return G.battleActors().player end
function G.battleOwnSoldiers() return G.battleActors().own end
function G.battleEnemies() return G.battleActors().enemies end
function G.taskInvincible()
  local bp=G.battlePawn()
  if bp then G.pawnFull(bp) G.statFull(bp,'Stat.Mana') end
  local u=G.player(); if not u then return end
  local mx=UE.get(u,'dv_HPMax')
  local hp,php=UE.get(u,'HPCurrent')
  if mx and hp and mx&gt;0 and hp&lt;mx then UE.wr(u+php.off, php, mx) end
  for _,k in ipairs({'CurrentHealth','CurrentSanity'}) do local v,p=UE.get(u,k) if v and v&lt;10 then UE.wr(u+p.off,p,10) end end
end
-- keep a float/double at base*mult, re-reading base when the game changes the value
local function scaled(st, key, a, rdf, wrf, mult)
  local cur=rdf(a); if not cur then return end
  local s=st[key]
  if not s or s.a~=a then s={a=a, base=cur} st[key]=s end
  if s.last and math.abs(cur-s.last)&gt;0.01 then s.base=cur end
  local want=s.base*mult
  if math.abs(cur-want)&gt;0.01 then wrf(a, want) end
  s.last=want
end
-- the pawn the player controls right now (field actor or the realtime-battle pawn)
--   GameInstance.LocalPlayers[0].PlayerController.Pawn : always the live object (no stale actor of an earlier level)
function G.curPawn()
  local gi=cached('gi', G.C.gi, function() return UE.instances(G.C.gi)[1] end, 'noscan')
  if not gi then return nil end
  local a=UE.addr(gi,'LocalPlayers'); if not a then return nil end
  local d,n=UE.arr(a); if d==0 or n&lt;1 then return nil end
  local lp=rq(d); if not lp or lp==0 then return nil end
  local pc=UE.get(lp,'PlayerController'); if not pc or pc==0 then return nil end
  local pw=UE.get(pc,'Pawn') if not pw or pw==0 then pw=UE.get(pc,'AcknowledgedPawn') end
  if not pw or pw==0 or not rq(pw+0x10) or UE.isCDO(pw) then return nil end
  return pw
end
function G.playerActor(mode)
  local pw=G.curPawn()
  if pw and G.valid(pw,G.C.pactor) then G.cache.pactor=pw return pw end
  return cached('pactor', G.C.pactor, function() return UE.instances(G.C.pactor)[1] end, mode)
end
local function rdN(a,p) if p.t=='DoubleProperty' then return readDouble(a) elseif p.t=='IntProperty' then return ri(a) end return readFloat(a) end
local function wrN(a,p,v) if p.t=='DoubleProperty' then writeDouble(a,v) elseif p.t=='IntProperty' then writeInteger(a,math.floor(v)) else writeFloat(a,v) end end
-- speed members of a pawn: CharacterMovement.MaxWalkSpeed (field and battle) and DefaultMovSpeed (battle pawn)
function G.pawnSpeedSlots(pw)
  local list={}
  local mc=UE.get(pw,'CharacterMovement')
  if mc and mc~=0 then local q=UE.P(mc,'MaxWalkSpeed') if q then list[#list+1]={o=mc, p=q} end end
  local q=UE.P(pw,'DefaultMovSpeed') if q then list[#list+1]={o=pw, p=q} end
  return list
end
-- keep v &gt;= base*mult. base = highest value the GAME wrote (capped at first value * run multiplier, so our own
-- writes or the game's interpolation from our value never compound)
local function floorSpeed(key, a, p, mult, run)
  local cur=rdN(a,p); if not cur or cur~=cur then return end
  local s=G.st.spd[key]
  if not s or s.a~=a then s={a=a, p=p, base=cur, cap=cur*run*1.01} G.st.spd[key]=s end
  local ours=s.last and math.abs(cur-s.last)&lt;0.01
  if s.base&lt;=0 and cur&gt;0 and not ours then s.base=cur s.cap=cur*run*1.01 end
  if not ours and cur&gt;s.base and cur&lt;=s.cap then s.base=cur end
  local want=s.base*mult
  if cur&lt;want*0.99 then wrN(a,p,want) s.last=want elseif not ours then s.last=nil end
end
function G.taskPSpeed()
  local mult=readFloat(GT.var(0x20)) or 1
  if mult&lt;=0 then return end
  G.st.spd=G.st.spd or {}
  local pm=cached('pm','ParameterManager_C', function() return UE.instances('ParameterManager_C')[1] end, 'noscan')
  local run=(pm and UE.get(pm,'RunMulti')) or 2
  if not run or run&lt;1 or run&gt;10 then run=2 end
  local bp=G.battlePawn()
  local pw=bp or G.curPawn() or G.playerActor('noscan')
  if not pw then return end
  for _,e in ipairs(G.pawnSpeedSlots(pw)) do
    floorSpeed(('%X'):format(e.o+e.p.off), e.o+e.p.off, e.p, mult, run)
  end
  if bp then
    for _,tag in ipairs({'Stat.MovementSpeed','Stat.SprintSpeed','Stat.StrafeSpeed'}) do
      local a,p=G.statAddr(bp, tag)
      if a then floorSpeed(('%X'):format(a), a, p, mult, 1) end
    end
  end
end
function G.taskStamina()
  local seen={}
  for _,o in ipairs({G.curPawn() or 0, G.playerActor('noscan') or 0}) do
    if o~=0 and not seen[o] then seen[o]=true
      -- only the members confirmed in the field actor dump are written (StaminaC/StaminaMaxC, StaminaP/StaminaMaxP)
      for _,k in ipairs({{'StaminaC','StaminaMaxC'},{'StaminaP','StaminaMaxP'}}) do
        local v,p=UE.get(o,k[1]) local mx=UE.get(o,k[2])
        if v and mx and mx&gt;0 and v&lt;mx then UE.wr(o+p.off,p,mx) end
      end
    end
  end
end
function G.restorePSpeed()
  local pw=G.curPawn()
  for _,s in pairs(G.st.spd or {}) do
    -- only members of the live pawn are restored (memory of destroyed actors is never written)
    if pw and s.base and s.last then
      for _,e in ipairs(G.pawnSpeedSlots(pw)) do if e.o+e.p.off==s.a then pcall(wrN, s.a, s.p, s.base) end end
    end
  end
  G.st.spd=nil
end
function G.taskFloor(name, v)
  local u=G.player(); if not u or not v then return end
  local cur,p=UE.get(u,name)
  if cur and cur&lt;v then UE.wr(u+p.off,p,v) end
end
function G.taskAge()
  local u=G.player(); if not u then return end
  local age=UE.get(u,'dv_Age'); if not age then return end
  local s=G.st.age
  if not s or s.u~=u then s={u=u, age=age} G.st.age=s end
  if age&gt;s.age then
    local by=UE.get(u,'BirthYear')
    if by then UE.set(u,'BirthYear', by+(age-s.age)) end
    UE.set(u,'dv_Age', s.age)
  end
end
function G.allBSKeys()
  local keys={}
  for _,o in ipairs(G.map('unit').list) do
    local a,p=UE.addr(o,'GeneralStringMap')
    if a and p.key then for _,e in ipairs(UE.mapEntries(a,p)) do local k=UE.fstr(e.e) if k:sub(1,3)=='BS_' then keys[k]=true end end end
  end
  for _,s in ipairs(GT.BSKILLS) do keys['BS_'..s[1]..'_1']=true end
  local r={} for k in pairs(keys) do r[#r+1]=k end table.sort(r)
  return r
end
-- max level of every battle skill: longest per-level array in DT_BattleSkill (Duration / CoolTime / ModifingParams)
-- and the highest "BS_&lt;skill&gt;_&lt;lv&gt;" key owned by any person
function G.skillMaxKeys()
  local mx={}
  for _,k in ipairs(G.allBSKeys()) do
    local name,lv=k:match('^BS_(.+)_(%d+)$')
    if name then lv=tonumber(lv) if not mx[name] or lv&gt;mx[name] then mx[name]=lv end end
  end
  local dt=UE.dataTable('DT_BattleSkill')
  if dt and dt.struct then
    local pid=UE.PS(dt.struct,'SkillNameID')
    local arrs={} for _,n in ipairs({'Duration','CoolTime','ModifingParams'}) do local q=UE.PS(dt.struct,n) if q and q.t=='ArrayProperty' then arrs[#arrs+1]=q end end
    for _,r in ipairs(dt.rows) do
      if r.ptr then
        local name=(pid and UE.fname(r.ptr+pid.off)) or r.name
        local n=1
        for _,q in ipairs(arrs) do local c=ri(r.ptr+q.off+8) or 0 if c&gt;n and c&lt;=20 then n=c end end
        if not mx[name] or n&gt;mx[name] then mx[name]=n end
      end
    end
  end
  -- the skill screen shows Lv1-Lv5 for every battle skill, so 5 is the minimum maximum level
  for _,b in ipairs(GT.BSKILLS) do mx[b[1]]=mx[b[1]] or 5 end
  for name,n in pairs(mx) do if n&lt;5 then mx[name]=5 end end
  local keys={}
  for name,n in pairs(mx) do for lv=1,n do keys[#keys+1]=('BS_%s_%d'):format(name,lv) end end
  table.sort(keys)
  return keys
end
function G.setSkillKeys(keys, en, ja)
  G.fresh()
  local u=G.needPlayer()
  local a,p=UE.addr(u,'GeneralStringMap')
  if not a or not p.key or p.key.t~='StrProperty' or p.val.t~='StrProperty' then GT.err('GeneralStringMap not found.','GeneralStringMapが見つかりません。') end
  local added=0
  for _,k in ipairs(keys) do
    local v=UE.mapFindStr(a,p,k)
    if not v then UE.mapSetStr(a,p,k,UE.newFStr('True')) added=added+1
    elseif UE.fstr(v)~='True' then UE.setFStr(v,'True') added=added+1 end
  end
  GT.msg((en..' %d entries set. Open the skill screen again to refresh.'):format(added),(ja..' %d件を設定しました。スキル画面を開き直してください。'):format(added))
end
function G.maxAllSkills() G.fresh() G.setSkillKeys(G.skillMaxKeys(), 'All battle skills at max level:', '全戦闘スキル最大レベル:') end
function G.learnAllSkills()
  G.fresh()
  local u=G.needPlayer()
  local a,p=UE.addr(u,'GeneralStringMap')
  if not a or not p.key or p.key.t~='StrProperty' or p.val.t~='StrProperty' then GT.err('GeneralStringMap not found.','GeneralStringMapが見つかりません。') end
  local added=0
  for _,k in ipairs(G.allBSKeys()) do
    local v=UE.mapFindStr(a,p,k)
    if not v then UE.mapSetStr(a,p,k,UE.newFStr('True')) added=added+1
    elseif UE.fstr(v)~='True' then UE.setFStr(v,'True') added=added+1 end
  end
  GT.msg(('Battle skills: %d learned. Open the skill screen again to refresh.'):format(added),
         ('戦闘スキル: %d件を習得しました。スキル画面を開き直してください。'):format(added))
end
function G.maxAbilities() G.fresh() G.maxAbilitiesFor(G.needPlayer()) end
function G.maxAbilitiesFor(u)
  local v=readInteger(GT.var(0x24)) or 0
  if v&lt;=0 then v=math.floor(UE.dtMax('DT_UnitExpSkillThreashold') or 999999) end
  local n=0
  for _,ab in ipairs(G.ABIL) do if UE.set(u, ab[1]..'Exp', v) then n=n+1 end end
  GT.msg(('%d ability EXP values set to %d. Levels follow when the game recalculates them.'):format(n,v),
         ('能力経験値 %d件を %d にしました。レベルはゲームの再計算時に反映されます。'):format(n,v))
end
-- a technology counts as acquired when SpentScience reaches NeededScience of DT_TechnologyTypes
-- (IsUnblocked only means "researchable": in save data a tech with 0 science spent can be IsUnblocked=true)
function G.techNeeded()
  local r={}
  local dt=UE.dataTable('DT_TechnologyTypes')
  if dt and dt.struct then
    local pid,pn=UE.PS(dt.struct,'ID'),UE.PS(dt.struct,'NeededScience')
    if pn then for _,row in ipairs(dt.rows) do if row.ptr then
      local id=pid and UE.fstr(row.ptr+pid.off) or row.name
      local v=UE.rd(row.ptr+pn.off,pn)
      r[id]=v r[row.name]=r[row.name] or v
    end end end
  end
  return r
end
function G.allTech()
  G.fresh()
  local c=G.needCountry()
  local a,p=UE.addr(c,'TechnologyTreeIns')
  if not a or not p.inner or not p.inner.struct then GT.err('TechnologyTreeIns not found.','TechnologyTreeInsが見つかりません。') end
  local st,es=p.inner.struct,p.inner.size
  local pid,ps,pu,pr=UE.PS(st,'TechnologyTypeID'),UE.PS(st,'SpentScience'),UE.PS(st,'IsUnblocked'),UE.PS(st,'IsInResearch')
  if not (pid and ps) then GT.err('Technology fields not found.','技術の項目が見つかりません。') end
  local need=G.techNeeded()
  local d,n=UE.arr(a) local k=0 local miss=0
  for i=0,n-1 do
    local e=d+i*es
    local id=UE.fstr(e+pid.off)
    local nv=need[id]
    if not nv then miss=miss+1 nv=99999 end
    local cur=UE.rd(e+ps.off,ps) or 0
    if cur&lt;nv then UE.wr(e+ps.off,ps,nv) k=k+1 end
    if pu then UE.wr(e+pu.off,pu,true) end
    if pr then UE.wr(e+pr.off,pr,false) end
  end
  GT.msg(('Technologies: %d completed now (%d total%s). Reopen the technology screen.'):format(k,n, miss&gt;0 and (', '..miss..' without DT cost') or ''),
         ('技術: %d件を完了にしました（全%d件%s）。技術画面を開き直してください。'):format(k,n, miss&gt;0 and ('、必要科学値不明'..miss..'件') or ''))
end
G.UNITFIELDS = {
  {'Money','Money','所持金'},{'Influence','Influence','影響度'},{'Renown','Renown','名声'},
  {'HPCurrent','HP','HP'},{'dv_HPMax','Max HP','最大HP'},{'CurrentHealth','Health','健康'},{'CurrentSanity','Sanity','正気'},
  {'LeadershipLV_LastCache','Leadership Lv (cache)','統率Lv（キャッシュ）'},{'PoliticsLV_LastCache','Politics Lv (cache)','政治Lv（キャッシュ）'},{'InteligenceLV_LastCache','Intelligence Lv (cache)','知力Lv（キャッシュ）'},
  {'BattleLV_LastCache','Battle Lv (cache)','武力Lv（キャッシュ）'},{'IntuitionLV_LastCache','Intuition Lv (cache)','直感Lv（キャッシュ）'},{'CharmLV_LastCache','Charm Lv (cache)','魅力Lv（キャッシュ）'},
  {'LeadershipExp','Leadership EXP','統率経験値'},{'PoliticsExp','Politics EXP','政治経験値'},{'InteligenceExp','Intelligence EXP','知力経験値'},
  {'BattleExp','Battle EXP','武力経験値'},{'IntuitionExp','Intuition EXP','直感経験値'},{'CharmExp','Charm EXP','魅力経験値'},
  {'FocusPoint','Focus points','フォーカスポイント'},{'Rank','Rank','ランク'},{'dv_Tier','Tier','階級'},
  {'dv_Age','Age','年齢'},{'BirthYear','Birth year','生年'},{'Tolerance','Tolerance','寛容'},{'dv_hate','Hatred','憎悪'},
  {'IsPregnant','Pregnant','妊娠中'},{'dv_IsDead','Dead','死亡'},
}
function G.openEditor(parent, build)
  GT.clearChildren(parent)
  local ok, err = pcall(build, parent)
  if not ok then
    GT.clearChildren(parent)
    error(err, 0)
  end
  for i = 0, parent.Count - 1 do
    local group = parent.Child[i]
    if group.Count &gt; 0 then group.Collapsed = true end
  end
  parent.Collapsed = false
end

function G.listPlayer(mr)
  G.fresh()
  local player = G.needPlayer()
  GT.addHdr(mr, G.unitName(player), G.unitName(player))
  local shown = {}
  for _, category in ipairs(G.UNITCAT) do
    local fields = {}
    for _, field in ipairs(category[2]) do
      local property = UE.P(player, field[1])
      if property and not shown[property.off] then
        fields[#fields+1] = field
        shown[property.off] = true
      end
    end
    if #fields &gt; 0 then
      local group = GT.addHdr(mr, category[1][1], category[1][2])
      G.rows(group, 0, player, fields)
    end
  end
  local fields = {}
  for _, field in ipairs(G.UNITFIELDS) do
    local property = UE.P(player, field[1])
    if property and not shown[property.off] then fields[#fields+1] = field end
  end
  if #fields &gt; 0 then
    G.rows(GT.addHdr(mr, 'Other parameters', 'その他の項目'), 0, player, fields)
  end
end

----------------------------------------------------------------- 2. army
function G.soldiers(army)
  local a,p=UE.addr(army,'MemberSoldiers')
  if not a or not p.inner then return {}, nil, nil end
  local d,n=UE.arr(a) local r={}
  if p.inner.t=='ObjectProperty' then
    for i=0,n-1 do local o=rq(d+i*8) if o then r[#r+1]={obj=o, i=i} end end
  elseif p.inner.t=='StructProperty' then
    local es=p.inner.size
    for i=0,n-1 do r[#r+1]={base=d+i*es, st=p.inner.struct, i=i} end
  end
  return r, p, a
end
function G.sP(s, name) if s.obj then return UE.P(s.obj,name), s.obj else return UE.PS(s.st,name), s.base end end
function G.sGet(s, name) local p,b=G.sP(s,name) if p then return UE.rd(b+p.off,p),p,b end end
function G.sSet(s, name, v) local p,b=G.sP(s,name) if p then UE.wr(b+p.off,p,v) return true end return false end
function G.taskArmy(what)
  local a=G.playerArmy(); if not a then return end
  if what=='cost' then local v,p=UE.get(a,'dv_WageTotal') if v and v~=0 then UE.wr(a+p.off,p,0) end
  elseif what=='food' then local v,p=UE.get(a,'Enough Food?') if p and not v then UE.wr(a+p.off,p,true) end
  elseif what=='morale' then local w=readInteger(GT.var(0x38)) or 100 local v,p=UE.get(a,'Morale') if v and v&lt;w then UE.wr(a+p.off,p,w) end end
end
function G.taskArmySpeed()
  local a=G.playerArmy(); if not a then return end
  local p=UE.P(a,'dv_Mobility'); if not p or p.t~='DoubleProperty' then return end
  scaled(G.st,'aSpeed', a+p.off, readDouble, writeDouble, readFloat(GT.var(0x3C)) or 1)
end
function G.restoreArmySpeed() local s=G.st.aSpeed if s and s.base then pcall(writeDouble, s.a, s.base) end G.st.aSpeed=nil end
function G.armyUnits()
  local r={} local u=G.player() if u then r[#r+1]=u end
  local a=G.playerArmy()
  if a then for _,id in ipairs(UE.strArr(a,'MemberUnitIDs') or {}) do local x=G.byId('unit',id) if x then r[#r+1]=x end end end
  return r
end
function G.isFood(id) return id:find('食料',1,true)~=nil end
function G.taskFoodFreeze()
  local st=G.st.food or {} G.st.food=st
  for _,u in ipairs(G.armyUnits()) do
    local a,p=UE.addr(u,'TradeGoods')
    if a and p.inner and p.inner.struct then
      local es=p.inner.size
      local pid,pc=UE.PS(p.inner.struct,'TradeGoodsTypeStringID'),UE.PS(p.inner.struct,'Count')
      if pid and pc then
        local d,n=UE.arr(a)
        for i=0,n-1 do
          local e=d+i*es local id=UE.fstr(e+pid.off)
          if G.isFood(id) then
            local key=u..'|'..id local c=ri(e+pc.off) or 0
            if not st[key] or c&gt;st[key] then st[key]=c elseif c&lt;st[key] then writeInteger(e+pc.off, st[key]) end
          end
        end
      end
    end
  end
end
-- your soldiers in the realtime battle: stamina (Stat.Mana, the only stat besides Stat.Health that has a maximum)
function G.taskArmyStamina()
  for _,x in ipairs(G.battleOwnSoldiers()) do G.statFull(x,'Stat.Mana') end
end
function G.taskArmyHP()
  for _,x in ipairs(G.battleOwnSoldiers()) do G.pawnFull(x) end
  local a=G.playerArmy(); if not a then return end
  local st=G.st.shp or {} G.st.shp=st
  for _,s in ipairs((G.soldiers(a))) do
    local hp,p,b=G.sGet(s,'SoldierHP|HP')
    if hp then
      local key=(s.obj or s.base)
      local mx=G.sGet(s,'dv_HPMax') or G.sGet(s,'HPMax')
      if not mx or mx&lt;=0 then mx=st[key] end
      if not mx or hp&gt;mx then st[key]=hp mx=hp end
      if hp&lt;mx then UE.wr(b+p.off,p,mx) end
    end
  end
end
function G.newId()
  local cs='abcdefghijklmnopqrstuvwxyz0123456789' local t={}
  for i=1,10 do local k=math.random(1,#cs) t[i]=cs:sub(k,k) end
  return table.concat(t)
end
function G.pickSoldierType(title, allowSame, back)
  local names={} local ids={}
  if allowSame then names[1]=T('(same type as the template soldier)','（見本の兵士と同じ兵種）') ids[1]=false end
  for _,x in ipairs(GT.SOLDIERTYPES) do names[#names+1]=L2(x) ids[#ids+1]=x[1] end
  local i=UI.select(title, names, {back=back})
  if i==nil or i=='back' then return i end
  return ids[i]
end
function G.addSoldiers()
  G.fresh()
  local army=G.needArmy()
  local list,p,a=G.soldiers(army)
  if not p then GT.err('MemberSoldiers not found.','MemberSoldiersが見つかりません。') end
  local tpl=list[1] or GT.err('Your army needs at least one soldier (used as a template).','見本として兵士が1人以上必要です。')
  local typ, n
  while true do
    typ=G.pickSoldierType(T('Add soldiers: type','兵士追加：兵種を選択'), true, false)
    if typ==nil or typ=='back' then return end
    n=UI.askInt(T('Add soldiers','兵士追加'), T('How many soldiers? (1-500)','何人追加しますか？（1～500）'), readInteger(GT.var(0x40)) or 10, 1, 500)
    if n then break end
  end
  writeInteger(GT.var(0x40), n)
  for k=1,n do
    if tpl.obj then
      local cls=rq(tpl.obj+0x10)
      local no=UE.spawnObject(cls, rq(tpl.obj+0x20) or army)
      UE.copyProps(tpl.obj, no, cls)
      UE.set(no,'ID',G.newId())
      if typ then UE.set(no,'FinalNameID',typ) end
      UE.arrAppend(a, 8, UE.le(no,8))
    else
      local ov={ID=G.newId()} if typ then ov.FinalNameID=typ end
      UE.arrAppend(a, p.inner.size, UE.cloneStruct(tpl.st, tpl.base, p.inner.size, ov))
    end
  end
  GT.msg(('%d soldiers added.'):format(n),('兵士を%d人追加しました。'):format(n))
end
function G.changeSoldierType()
  G.fresh()
  local army=G.needArmy()
  local list=G.soldiers(army)
  if #list==0 then GT.err('Your army has no soldiers.','部隊に兵士がいません。') end
  local cnt={} local order={}
  for _,s in ipairs(list) do local id=G.sGet(s,'FinalNameID') or '?' if not cnt[id] then cnt[id]=0 order[#order+1]=id end cnt[id]=cnt[id]+1 end
  local names={}
  for k,id in ipairs(order) do names[k]=('%s   x%d'):format(G.soldierTypeName(id), cnt[id]) end
  while true do
    local sel=UI.select(T('Change soldier type: soldiers to change (several allowed)','兵種変更：変更する兵士の兵種（複数可）'), names, {multi=true})
    if not sel or sel=='back' then return end
    local from={} for _,k in ipairs(sel) do from[order[k]]=true end
    local to=G.pickSoldierType(T('Change soldier type: new type','兵種変更：新しい兵種'), false, true)
    if to==nil then return end
    if to~='back' then
      local k=0
      for _,s in ipairs(list) do if from[G.sGet(s,'FinalNameID') or '?'] then if G.sSet(s,'FinalNameID',to) then k=k+1 end end end
      GT.msg(('%d soldiers changed to %s.'):format(k,G.soldierTypeName(to)),('%d人を%sに変更しました。'):format(k,G.soldierTypeName(to)))
      return
    end
  end
end
function G.soldierLevelMax()
  G.fresh()
  local list=G.soldiers(G.needArmy())
  local lv=readInteger(GT.var(0x44)) or 10
  local ex=UE.dtMax('DT_SoldierExpThreashold')
  local k=0
  for _,s in ipairs(list) do
    if G.sSet(s,'dv_Level',lv) then k=k+1 end
    if ex then local v,p,b=G.sGet(s,'Experience') if v and v&lt;ex then UE.wr(b+p.off,p,ex) end end
  end
  GT.msg(('%d soldiers: level %d%s.'):format(k,lv, ex and (', EXP '..ex) or ''),('%d人をレベル%dにしました%s。'):format(k,lv, ex and ('（経験値'..ex..'）') or ''))
end
G.ARMYFIELDS = {
  {'Morale','Morale','士気'},{'dv_Mobility','Mobility','機動力'},{'dv_Sight','Sight','視界'},{'dv_WageTotal','Total wage','給与合計'},
  {'MaxWage','Max wage','最大給与'},{'dv_MaxMember','Max officers','最大武将数'},{'InfiniteWage','Infinite wage','給与無制限'},
  {'Enough Food?','Enough food','食料充足'},{'Is in Field','In field','野外'},
}
function G.listArmy(mr)
  G.fresh()
  local a=G.needArmy()
  local list=G.soldiers(a)
  GT.addHdr(mr, ('Soldiers: %d'):format(#list), ('兵士数: %d'):format(#list))
  G.rows(GT.addHdr(mr,'Army parameters','部隊パラメーター'), 1, a, G.ARMYFIELDS)
end
function G.listSoldiers(mr)
  G.fresh()
  local a=G.needArmy()
  local list,p=G.soldiers(a)
  if #list==0 then GT.addHdr(mr,'(no soldiers)','（兵士なし）') return end
  local pa=p.off
  for _,s in ipairs(list) do
    local nm=G.soldierTypeName(G.sGet(s,'FinalNameID') or '?')
    local h=GT.addHdr(mr, ('#%d %s'):format(s.i+1,nm), ('#%d %s'):format(s.i+1,nm))
    for _,f in ipairs({{'dv_Level','Level','レベル'},{'Experience','EXP','経験値'},{'SoldierHP|HP','HP','HP'},{'dv_HPMax','Max HP','最大HP'}}) do
      local q=G.sP(s,f[1])
      if q then
        local addr
        if s.obj then addr=('[[[gtPtrs+8]+%X]+%X]+%X'):format(pa, s.i*8, q.off)
        else addr=('[[gtPtrs+8]+%X]+%X'):format(pa, s.i*p.inner.size+q.off) end
        GT.addVal(h, f[2], f[3], addr, q)
      end
    end
  end
end

----------------------------------------------------------------- 3. cities
function G.seizeSpot()
  G.fresh()
  local pw=G.needPower()
  local my=G.id(pw)
  local spots=G.pickSpots(T('Seize cities (several allowed)','都市強奪：統治下にする都市（複数可）'), function(s,owner) return not owner or G.id(owner)~=my end, {multi=true})
  if not spots or spots=='back' then return end
  G.doSeize(spots)
end
function G.doSeize(spots)
  local pw=G.needPower() local my=G.id(pw)
  local owners=G.spotOwners()
  local done={}
  for _,s in ipairs(spots) do
    local sid=G.id(s)
    local old=owners[sid]
    if old and G.id(old)~=my then
      for _,c in ipairs(G.map('country').list) do
        if UE.get(c,'CapitalSpotID')==sid and not UE.get(c,'IsPerished') then
          GT.msg(('Note: %s is the capital of a country.'):format(G.spotName(sid)),('注意: %s はいずれかの国の首都です。'):format(G.spotName(sid)))
        end
      end
      UE.strArrRemove(old,'MemberSpotIDs',sid)
    end
    UE.strArrAdd(pw,'MemberSpotIDs',sid)
    if UE.P(s,'BelongingPowerID_LastCache') then UE.set(s,'BelongingPowerID_LastCache',my) end
    done[#done+1]=G.spotName(sid)
  end
  GT.msg(('Now yours: %s'):format(table.concat(done,', ')),('統治下にしました: %s'):format(table.concat(done,'、')))
end
-- vacate cities: remove them from the owning faction (the current lord is dismissed; the city has no lord afterwards)
function G.vacateSpots()
  G.fresh()
  local spots=G.pickSpots(T('Vacate cities: dismiss the current lord (several allowed; only cities that have a lord are listed)',
                            '都市空白：現在の領主を強制解雇する都市（複数可・領主のいる都市のみ表示）'),
                          function(s,owner) return owner~=nil end, {multi=true})
  if not spots or spots=='back' then return end
  G.doVacate(spots)
end
function G.doVacate(spots)
  local owners=G.spotOwners()
  local done={}
  for _,s in ipairs(spots) do
    local sid=G.id(s)
    local old=owners[sid]
    if old then
      for _,c in ipairs(G.map('country').list) do
        if UE.get(c,'CapitalSpotID')==sid and not UE.get(c,'IsPerished') then
          GT.msg(('Note: %s is the capital of a country.'):format(G.spotName(sid)),('注意: %s はいずれかの国の首都です。'):format(G.spotName(sid)))
        end
      end
      UE.strArrRemove(old,'MemberSpotIDs',sid)
      if UE.P(s,'BelongingPowerID_LastCache') then UE.set(s,'BelongingPowerID_LastCache','') end
      done[#done+1]=('%s (%s)'):format(G.spotName(sid), G.powerName(old))
    end
  end
  GT.msg(('Vacated: %s'):format(table.concat(done,', ')),('空白にしました（解雇した領主）: %s'):format(table.concat(done,'、')))
end
-- a spot value exists as &lt;base&gt;_Raw / _RawFix (stored) and &lt;base&gt;_Final (shown, recalculated daily); write all that exist
function G.spotSet(s, base, v, mode)
  local n=0
  for _,suf in ipairs({'_Raw','_RawFix','_Final',''}) do
    local cur,p=UE.get(s, base..suf)
    if p and (p.t=='DoubleProperty' or p.t=='IntProperty') then
      if mode=='min' and cur&gt;v then UE.wr(s+p.off,p,v) n=n+1
      elseif mode=='max' and cur&lt;v then UE.wr(s+p.off,p,v) n=n+1
      elseif not mode and cur~=v then UE.wr(s+p.off,p,v) n=n+1 end
    end
  end
  return n
end
-- wall maximum: wall facility level x WallLevelToDefenceMulti when both are known, else the parameter value
function G.wallMax(s)
  local mul=UE.get(s,'WallLevelToDefenceMulti')
  local a,p=UE.addr(s,'Facility')
  if mul and mul&gt;0 and a and p.inner and p.inner.struct then
    local pid,plv=UE.PS(p.inner.struct,'StringID'),UE.PS(p.inner.struct,'CurrentLevel')
    local d,n=UE.arr(a)
    for i=0,n-1 do
      local e=d+i*p.inner.size
      if UE.fstr(e+pid.off)=='城壁' then local lv=ri(e+plv.off) or 0 if lv&gt;0 then return math.max(lv*mul, readDouble(GT.var(0x48)) or 0) end end
    end
  end
  return readDouble(GT.var(0x48)) or 500
end
-- city impression of the player: SpotDataClass_C UnitImpression[playerID] (also PowerImpression / CountryImpression on request)
function G.spotImpression()
  G.fresh()
  local me=G.needPlayer() local pid=G.id(me)
  local pw=G.playerPower() local c=G.playerCountry()
  local spots=G.pickSpots(T('City impression: cities (several allowed)','都市の印象変更：対象の都市（複数可）'), nil, {multi=true})
  if not spots or spots=='back' then return end
  G.doSpotImpression(spots)
end
function G.doSpotImpression(spots)
  local me=G.needPlayer() local pid=G.id(me)
  local pw=G.playerPower() local c=G.playerCountry()
  local tg={ {'UnitImpression',pid,T('Impression of you (person)','あなた（人物）への印象')} }
  if pw then tg[#tg+1]={'PowerImpression',G.id(pw),T('Impression of your faction','自勢力への印象')} end
  if c then tg[#tg+1]={'CountryImpression',G.id(c),T('Impression of your country','自国への印象')} end
  local names={} for k,t in ipairs(tg) do names[k]=t[3] end
  local sel=UI.select(T('City impression: what to change','都市の印象変更：変更する印象'), names, {multi=true, checked={1}})
  if not sel or sel=='back' then return end
  local v=UI.askInt(T('City impression','都市の印象変更'), T('New impression value (0-100)','新しい印象の値（0～100）'), 100, 0, 100)
  if not v then return end
  local done={}
  for _,s in ipairs(spots) do
    for _,k in ipairs(sel) do
      local t=tg[k]
      local a,p=UE.addr(s,t[1])
      if a and p.key and p.val and p.val.t=='DoubleProperty' then UE.mapSetStr(a,p,t[2],UE.dbl(v)) end
    end
    done[#done+1]=G.spotName(G.id(s))
  end
  GT.msg(('Impression set to %d: %s'):format(v,table.concat(done,', ')),('印象を%dにしました: %s'):format(v,table.concat(done,'、')))
end
function G.normalizeSpot(s)
  G.spotSet(s,'WarDevastation',0.0,'min')
  G.spotSet(s,'Safety',100.0,'max')
  local w=G.wallMax(s)
  local v,p=UE.get(s,'WallDefence') if v and v&lt;w then UE.wr(s+p.off,p,w) end
end
-- pick cities: all cities are listed, your own cities are ticked in advance
function G.pickOwnDefault(title)
  local pw=G.needPower() local my=G.id(pw)
  local owners=G.spotOwners()
  local list={} for _,s in ipairs(G.map('spot').list) do list[#list+1]=s end
  table.sort(list, function(a,b) return (tonumber(G.id(a)) or 999)&lt;(tonumber(G.id(b)) or 999) end)
  local names,pre={},{}
  for i,s in ipairs(list) do
    local ow=owners[G.id(s)] local mine=ow and G.id(ow)==my
    local id=tonumber(G.id(s)) local st=id and GT.SPOTS[id+1] and G.SPOTTYPE[GT.SPOTS[id+1][3]]
    names[i]=('%s   |  %s  |  %s%s'):format(G.spotName(G.id(s)), st and L2(st) or '?', G.powerName(ow), mine and T(' (yours)','（自勢力）') or '')
    if mine then pre[#pre+1]=i end
  end
  local r=UI.select(title, names, {multi=true, checked=pre})
  if not r or r=='back' then return nil end
  local t={} for _,k in ipairs(r) do t[#t+1]=list[k] end
  return t
end
function G.normalizeOwnSpots(quiet)
  if quiet then
    local pw=G.playerPower(); if not pw then return end
    for _,sid in ipairs(UE.strArr(pw,'MemberSpotIDs') or {}) do local s=G.byId('spot',sid) if s then G.normalizeSpot(s) end end
    return
  end
  G.fresh()
  local l=G.pickOwnDefault(T('Normalize cities (your cities are ticked; change freely)','都市平常化：対象の都市（自勢力の都市にチェック済み・変更可）'))
  if not l then return end
  local nm={}
  for _,s in ipairs(l) do G.normalizeSpot(s) nm[#nm+1]=G.spotName(G.id(s)) end
  GT.msg(('Normalized: %s'):format(table.concat(nm,', ')),('平常化しました: %s'):format(table.concat(nm,'、')))
end
function G.facilityMaxTable()
  local mx={}
  for _,s in ipairs(G.map('spot').list) do
    local a,p=UE.addr(s,'Facility')
    if a and p.inner and p.inner.struct then
      local pid,plv=UE.PS(p.inner.struct,'StringID'),UE.PS(p.inner.struct,'CurrentLevel')
      local d,n=UE.arr(a)
      for i=0,n-1 do local e=d+i*p.inner.size local id=UE.fstr(e+pid.off) local lv=ri(e+plv.off) or 0 if not mx[id] or lv&gt;mx[id] then mx[id]=lv end end
    end
  end
  return mx
end
function G.facilities(levelFn, spots)
  if not spots then G.fresh() spots=G.ownSpots() end
  local k=0
  for _,s in ipairs(spots) do
    local a,p=UE.addr(s,'Facility')
    if a and p.inner and p.inner.struct then
      local st=p.inner.struct
      local pid,plv,puc,psc=UE.PS(st,'StringID'),UE.PS(st,'CurrentLevel'),UE.PS(st,'IsUnderConstruction'),UE.PS(st,'SpentConstruction')
      local d,n=UE.arr(a)
      for i=0,n-1 do
        local e=d+i*p.inner.size
        local id=UE.fstr(e+pid.off) local lv=ri(e+plv.off) or 0
        local want=levelFn(id, lv)
        if want and want&gt;lv then writeInteger(e+plv.off, want) k=k+1 end
        if puc then UE.wr(e+puc.off,puc,false) end
        if psc then UE.wr(e+psc.off,psc,0.0) end
      end
    end
  end
  return k, #spots
end
function G.buildAll()
  local k,n=G.facilities(function(id,lv) return math.max(lv,1) end)
  GT.msg(('%d facilities built in %d cities.'):format(k,n),('%d都市で%d施設を建設しました。'):format(n,k))
end
function G.facilityDTMax()
  local r={}
  local dt=UE.dataTable('DT_FacilityList')
  if dt and dt.struct then
    local pid,pmx=UE.PS(dt.struct,'StringID'),UE.PS(dt.struct,'MaxLevel')
    if pmx then for _,row in ipairs(dt.rows) do if row.ptr then r[pid and UE.fstr(row.ptr+pid.off) or row.name]=ri(row.ptr+pmx.off) r[row.name]=r[row.name] or ri(row.ptr+pmx.off) end end end
  end
  return r
end
function G.facilitiesMax(spots)
  local ov=readInteger(GT.var(0x50)) or 0
  local obs=G.facilityDTMax()
  if not next(obs) then obs=G.facilityMaxTable() end
  local k,n=G.facilities(function(id,lv) if ov&gt;0 then return ov end return math.max(obs[id] or 1, 1) end, spots)
  GT.msg(('%d facilities raised in %d cities.'):format(k,n),('%d都市で%d施設のレベルを上げました。'):format(n,k))
end
function G.developAll()
  G.fresh()
  local l=G.pickOwnDefault(T('Development max (your cities are ticked; change freely)','内政最大：対象の都市（自勢力の都市にチェック済み・変更可）'))
  if not l then return end
  local cm,ag=readDouble(GT.var(0x58)) or 5000,readDouble(GT.var(0x60)) or 2000
  local nm={}
  for _,s in ipairs(l) do
    G.spotSet(s,'Commerce',cm,'max') G.spotSet(s,'Agriculture',ag,'max')
    G.spotSet(s,'Loyalty',100.0) G.spotSet(s,'Safety',100.0) G.spotSet(s,'WarDevastation',0.0)
    local fs=readDouble(GT.var(0x68)) or 9999 local hr=readDouble(GT.var(0x70)) or 9999
    UE.set(s,'FoodSurplus',fs) UE.set(s,'HumanResource',hr)
    nm[#nm+1]=G.spotName(G.id(s))
  end
  GT.msg(('Developed: %s'):format(table.concat(nm,', ')),('内政値を上げました: %s'):format(table.concat(nm,'、')))
end
G.SPOTFIELDS = {
  {'Population','Population','人口'},{'Loyalty_Final','Loyalty (shown)','忠誠（表示値）'},{'Safety_Final','Safety (shown)','治安（表示値）'},{'WarDevastation_Final','War devastation (shown)','戦争荒廃（表示値）'},{'Commerce_Final','Commerce (shown)','商業（表示値）'},{'Agriculture_Final','Agriculture (shown)','農業（表示値）'},{'Loyalty_RawFix|Loyalty_Raw','Loyalty','忠誠'},{'Safety_Raw','Safety','治安'},{'WallDefence','Wall','城壁'},
  {'WarDevastation_Raw','War devastation','戦争荒廃'},{'Commerce_Raw','Commerce','商業'},{'Agriculture_Raw','Agriculture','農業'},
  {'Construction_Raw','Construction','建設力'},{'FoodSurplus','Food surplus','食料余剰'},{'HumanResource|dv_HumanResource_Raw','Manpower','人的資源'},
  {'dv_HumanResourceMax','Max manpower','最大人的資源'},{'MoneyToInvestCommerce','Commerce investment','商業投資'},
  {'dv_hate','Hatred','憎悪'},{'dv_IncomeToPower','Income to faction','勢力への収入'},
  {'InSiege','Under siege','包囲中'},{'PlayerVisited','Visited','訪問済み'},
}
function G.openCityEditor(parent)
  G.fresh()
  local city = G.pickSpot(T('Select a city to edit','編集する都市を選択'))
  if not city then
    GT.oneShot(parent,function() end)
    return
  end
  G.setSel('spot', city)
  G.openEditor(parent, G.editSpot)
end

----------------------------------------------------------------- 4. items
local function invInfo(u, prop)
  local a,p=UE.addr(u,prop)
  if not a or not p.inner or not p.inner.struct then return nil end
  return a,p,p.inner.struct,p.inner.size
end
function G.listInventory(mr)
  G.fresh()
  local u=G.needPlayer()
  local a,p,st,es=invInfo(u,'InventoryAllInstances')
  if a then
    local pid,pc,peq=UE.PS(st,'ItemTypeStringID'),UE.PS(st,'Count'),UE.PS(st,'IsEquiped')
    local groups, categories = {}, {}
    for _, item in ipairs(GT.ITEMS) do categories[item[1]] = item[3] end
    local d,n=UE.arr(a)
    for i=0,n-1 do
      local e=d+i*es
      local id=UE.fstr(e+pid.off)
      local nm=G.itemName(id)
      local key=categories[id] or ''
      if not groups[key] then
        local category=G.CATS[key] or {'Other items','その他のアイテム'}
        groups[key]=GT.addHdr(mr,category[1],category[2])
      end
      local h=groups[key]
      if peq and UE.rd(e+peq.off,peq) then nm=nm..T(' (equipped)','（装備中）') end
      GT.addVal(h, nm, nm, ('[[gtPtrs+0]+%X]+%X'):format(p.off, i*es+pc.off), pc)
    end
  end
  a,p,st,es=invInfo(u,'TradeGoods')
  if a then
    local pid,pc=UE.PS(st,'TradeGoodsTypeStringID'),UE.PS(st,'Count')
    local h=GT.addHdr(mr,'Trade goods / food','交易品・食料')
    local d,n=UE.arr(a)
    for i=0,n-1 do
      local e=d+i*es local nm=G.goodsName(UE.fstr(e+pid.off))
      GT.addVal(h, nm, nm, ('[[gtPtrs+0]+%X]+%X'):format(p.off, i*es+pc.off), pc)
    end
  end
end
G.CATS = { Body={'Body armor','鎧'}, Bow={'Bows','弓'}, Gun={'Guns','銃'}, Canon={'Cannons','大砲'}, Junk={'Materials / junk','素材・がらくた'},
           Quest={'Quest / special','クエスト・特殊'}, OneHandArmy={'One-handed weapons','片手武器'}, TwoHandArmy={'Two-handed weapons','両手武器'},
           Shield={'Shields','盾'}, UseItem={'Consumables','消耗品'}, Map={'Maps','地図'} }
local function addToInv(u, prop, idField, id, count, extra)
  local a,p,st,es=invInfo(u,prop)
  if not a then GT.err('Inventory not found: '..prop,'所持品が見つかりません: '..prop) end
  local pid,pc,peq=UE.PS(st,idField),UE.PS(st,'Count'),UE.PS(st,'IsEquiped')
  local d,n=UE.arr(a)
  for i=0,n-1 do
    local e=d+i*es
    if UE.fstr(e+pid.off)==id and not (peq and UE.rd(e+peq.off,peq)) then
      writeInteger(e+pc.off, (ri(e+pc.off) or 0)+count) return 'stacked'
    end
  end
  local vals={Count=count} vals[idField]=id
  for k,v in pairs(extra or {}) do vals[k]=v end
  UE.arrAppend(a, es, UE.makeStruct(st, es, vals))
  return 'added'
end
function G.closeItemAdd(parent)
  local session=G.itemAddSession
  G.itemAddSession=nil
  GT.clearChildren(parent)
  if session then
    for _,custom in ipairs(session.types) do custom.destroy() end
    if getOpenedProcessID()==session.pid then deAlloc(session.buffer) end
  end
end

function G.openItemAdd(parent)
  G.fresh()
  local player=G.needPlayer()
  G.closeItemAdd(parent)
  local session={pid=GT.pid,player=player,counts={},types={},buffer=assert(allocateMemory(4))}
  G.itemAddSession=session
  local ok,err=pcall(function()
    local action=GT.addRec(parent,'Apply additions','追加を実行','0',vtAutoAssembler)
    action.Color=GT.C_SCRIPT
    action.Script=[[
[ENABLE]
{$lua}
if syntaxcheck then return end
GT.oneShot(memrec,function() GT.G.applyItemAdds() end)
{$asm}
[DISABLE]
]]
    local groups={}
    for _,key in ipairs({'OneHandArmy','TwoHandArmy','Bow','Gun','Canon','Shield','Body','UseItem','Map','Junk','Quest'}) do
      local category=G.CATS[key]
      local group=GT.addHdr(parent,category[2],category[1])
      group.Collapsed=true
      groups[key]=group
    end
    G.itemAddSerial=(G.itemAddSerial or 0)+1
    for index,item in ipairs(GT.ITEMS) do
      local row=GT.addRec(groups[item[3]],item[2],item[1],('%X'):format(session.buffer),vtDword)
      local typeName=('EOH_Add_%d_%d_%d'):format(getTickCount(),G.itemAddSerial,index)
      local custom=assert(registerCustomTypeLua(typeName,4,function()
        return session.counts[index] or 0
      end,function(value)
        if G.itemAddSession==session and GT.attached() and GT.pid==session.pid then
          local n=tonumber(value)
          if n and n==n then session.counts[index]=math.max(0,math.min(9999,math.floor(n))) end
        end
        return {0,0,0,0}
      end,false,false))
      session.types[#session.types+1]=custom
      custom.InternalOnly=true
      row.Type=vtCustom
      row.CustomTypeName=typeName
      row.OnActivate=function(_,before) return not before end
    end
    parent.Collapsed=false
  end)
  if not ok then G.closeItemAdd(parent);error(err,0) end
end

function G.applyItemAdds()
  GT.require()
  local session=G.itemAddSession
  if not session or session.pid~=GT.pid then return end
  G.fresh()
  local player=G.needPlayer()
  if player~=session.player then
    GT.err('Player changed. Reopen item additions.','プレイヤーが変わりました。アイテム追加を開き直してください。')
  end
  for index,item in ipairs(GT.ITEMS) do
    local n=session.counts[index] or 0
    if n&gt;0 then
      addToInv(player,'InventoryAllInstances','ItemTypeStringID',item[1],n,
        {IsEquiped=false,Condition=0.0,BoughtPrice=0.0,ItemInstanceID=''})
      session.counts[index]=0
    end
  end
end
function G.addGoods()
  G.fresh()
  local u=G.needPlayer()
  local names={} for k,x in ipairs(GT.GOODS) do names[k]=L2(x)..(G.isFood(x[1]) and T('  [food]','　［食料］') or '') end
  while true do
    local r=UI.select(T('Add trade goods / food (several allowed)','交易品・食料の追加（複数可）'), names, {multi=true})
    if not r or r=='back' then return end
    local n=UI.askInt(T('Add trade goods','交易品の追加'), T('Quantity of each (1-99999)  - Cancel = back','それぞれの個数（1～99999）　※キャンセルで戻る'), 100, 1, 99999)
    if n then
      local got={}
      for _,k in ipairs(r) do addToInv(u,'TradeGoods','TradeGoodsTypeStringID',GT.GOODS[k][1],n,{BoughtPrice=1.0}) got[#got+1]=L2(GT.GOODS[k]) end
      GT.msg(('Added x%d: %s.'):format(n,table.concat(got,', ')),('%d個ずつ追加しました: %s'):format(n,table.concat(got,'、')))
      return
    end
  end
end

----------------------------------------------------------------- 5. persons
function G.recruit()
  G.fresh()
  local pw=G.needPower() local my=G.id(pw)
  local leaders={} for _,p in ipairs(G.map('power').list) do if not UE.get(p,'IsPerished') then leaders[UE.get(p,'LeaderUnitID') or '']=true end end
  local us=G.pickUnits(T('Force persons to join your faction (several allowed; faction leaders are not listed)','強制配下：配下にする人物（複数可・勢力の君主は対象外）'), function(u,owner)
    if UE.get(u,'IsPlayer') then return false end
    if owner and G.id(owner)==my then return false end
    return not leaders[G.id(u)]
  end, {multi=true})
  if not us or us=='back' then return end
  G.doRecruit(us)
end
function G.doRecruit(us)
  local pw=G.needPower() local my=G.id(pw)
  local done={}
  for _,u in ipairs(us) do
    local uid=G.id(u)
    for _,p in ipairs(G.map('power').list) do if G.id(p)~=my then UE.strArrRemove(p,'MemberUnitIDs',uid) end end
    UE.strArrAdd(pw,'MemberUnitIDs',uid)
    if UE.P(u,'BelongingPowerID_LastCache') then UE.set(u,'BelongingPowerID_LastCache',my) end
    done[#done+1]=G.unitName(u)
  end
  GT.msg(('Joined your faction: %s'):format(table.concat(done,', ')),('配下にしました: %s'):format(table.concat(done,'、')))
end
function G.marry()
  G.fresh()
  local me=G.needPlayer() local pid=G.id(me)
  local u=G.pickUnit(T('Marry: choose one person','結婚：相手を1人選択'), function(u) return not UE.get(u,'IsPlayer') end)
  if not u or u=='back' then return end
  G.doMarry(u)
end
function G.doMarry(u)
  local me=G.needPlayer() local pid=G.id(me)
  if u==me then GT.err('You cannot marry yourself.','自分自身とは結婚できません。') end
  local uid=G.id(u)
  local function clear(x) local sp=G.byId('unit', UE.get(x,'SpouseID')) if sp and sp~=me and sp~=u then UE.set(sp,'SpouseID','') end end
  clear(me) clear(u)
  UE.set(me,'SpouseID',uid) UE.set(u,'SpouseID',pid)
  GT.msg(('You married %s.'):format(G.unitName(u)),('%s と結婚しました。'):format(G.unitName(u)))
end
function G.impressionMax(u, pid, wid)
  local n=0
  local a,p=UE.addr(u,'UnitImpression')
  if a and p.key and p.val and p.val.t=='DoubleProperty' then UE.mapSetStr(a,p,pid,UE.dbl(100.0)) n=n+1 end
  if wid then
    a,p=UE.addr(u,'PowerImpression')
    if a and p.key and p.val and p.val.t=='DoubleProperty' then UE.mapSetStr(a,p,wid,UE.dbl(100.0)) n=n+1 end
  end
  return n
end
function G.vassalsImpression()
  G.fresh()
  local pid=G.id(G.needPlayer()) local pw=G.needPower() local wid=G.id(pw)
  local k=0
  for _,uid in ipairs(UE.strArr(pw,'MemberUnitIDs') or {}) do
    local u=G.byId('unit',uid)
    if u and uid~=pid then local ok=pcall(G.impressionMax,u,pid,wid) if ok then k=k+1 end end
  end
  GT.msg(('Impression of %d vassals set to 100.'):format(k),('配下%d人の好感度を100にしました。'):format(k))
end
function G.oneImpression()
  G.fresh()
  local pid=G.id(G.needPlayer()) local pw=G.playerPower()
  local us=G.pickUnits(T('Impression of you to 100 (several allowed)','好感度最大：対象の人物（複数可）'), function(u) return not UE.get(u,'IsPlayer') end, {multi=true})
  if not us or us=='back' then return end
  G.doImpression(us)
end
function G.doImpression(us)
  local pid=G.id(G.needPlayer()) local pw=G.playerPower()
  local done={}
  for _,u in ipairs(us) do G.impressionMax(u, pid, pw and G.id(pw)) done[#done+1]=G.unitName(u) end
  GT.msg(('Impression 100: %s'):format(table.concat(done,', ')),('好感度を100にしました: %s'):format(table.concat(done,'、')))
end
function G.listPerson(mr)
  G.fresh()
  local us=G.pickUnits(T('Person parameters: persons to show (several allowed)','人物パラメーター：表示する人物（複数可）'), nil, {multi=true})
  if not us or us=='back' then return end
  local pid=G.id(G.player() or 0)
  for _,u in ipairs(us) do
    local h=GT.addHdr(mr, G.unitName(u), G.unitName(u))
    G.rowsAbs(h, u, G.UNITFIELDS)
    local a,p=UE.addr(u,'UnitImpression')
    if a and p.key and pid~='' then
      local v=UE.mapFindStr(a,p,pid)
      if v then GT.addVal(h,'Impression of you','あなたへの好感度',('%X'):format(v),p.val) end
    end
  end
end
-- reveal hidden parameters (abilities / characteristics) of chosen persons
function G.revealSome()
  G.fresh()
  local us=G.pickUnits(T('Reveal details: persons (several allowed)','詳細解明：対象の人物（複数可）'), function(u) return not UE.get(u,'IsPlayer') end, {multi=true})
  if not us or us=='back' then return end
  G.doReveal(us)
end
function G.doReveal(us)
  local done={}
  for _,u in ipairs(us) do
    local k=0
    if UE.P(u,'ParamDisclosureToPlayer') then
      for _,key in ipairs(G.DISCLOSE) do if UE.strArrAdd(u,'ParamDisclosureToPlayer',key) then k=k+1 end end
    end
    done[#done+1]=('%s(%d)'):format(G.unitName(u),k)
  end
  GT.msg(('Details revealed (newly opened items): %s. Reopen the person screen to see them.'):format(table.concat(done,', ')),
         ('詳細を解明しました（新たに公開した項目数）: %s。人物画面を開き直すと反映されます。'):format(table.concat(done,'、')))
end
function G.revealAll()
  G.fresh()
  local k=0
  for _,u in ipairs(G.map('unit').list) do
    if UE.P(u,'ParamDisclosureToPlayer') then
      for _,key in ipairs(G.DISCLOSE) do if UE.strArrAdd(u,'ParamDisclosureToPlayer',key) then k=k+1 end end
    end
  end
  GT.msg(('%d hidden parameters revealed.'):format(k),('%d件の非公開パラメーターを公開しました。'):format(k))
end

----------------------------------------------------------------- 6. other
-- continue wars: CountryDataClass_C.WarFatigue (Map&lt;country ID, double&gt;) of your country (all entries) and the entries
-- of other countries for your country are kept 0.  Game text: "いずれかの国の戦争疲弊が100に達すると強制的に講和する".
function G.taskWarFatigue()
  local my=G.playerCountry('noscan'); if not my then return end
  local myId=G.id(my)
  for _,c in ipairs(G.map('country').list) do
    local a,p=UE.addr(c,'WarFatigue')
    if a and p.key and p.val and p.val.t=='DoubleProperty' then
      for _,e in ipairs(UE.mapEntries(a,p)) do
        if c==my or UE.fstr(e.e)==myId then
          local v=readDouble(e.v) if v and v~=0 then writeDouble(e.v, 0.0) end
        end
      end
    end
  end
end
function G.taskCollapse()
  for _,c in ipairs(G.map('country').list) do
    for _,k in ipairs({'dv_Division','dv_Hate'}) do local v,p=UE.get(c,k) if v and v~=0 then UE.wr(c+p.off,p,0.0) end end
  end
  for _,kind in ipairs({'unit','spot'}) do
    for _,o in ipairs(G.map(kind).list) do local v,p=UE.get(o,'dv_hate') if v and v~=0 then UE.wr(o+p.off,p,0.0) end end
  end
  G.st.dprops=G.st.dprops or {}
  for _,o in ipairs(UE.instances(G.C.dilemma)) do
    local cls=rq(o+0x10)
    local pl=G.st.dprops[cls]
    if not pl then
      pl={}
      for _,p in ipairs(UE.allInfo(cls)) do
        local n=p.cname
        if (p.t=='DoubleProperty' or p.t=='FloatProperty' or p.t=='IntProperty') and (n:find('Dilemma') or n:find('Collapse') or n:find('Madness'))
           and not (n:find('Max') or n:find('Limit') or n:find('Threash') or n:find('Thresh')) then pl[#pl+1]=p end
      end
      G.st.dprops[cls]=pl
    end
    for _,p in ipairs(pl) do local v=UE.rd(o+p.off,p) if v and v&gt;0 then UE.wr(o+p.off,p,0) end end
  end
end
-- date rows point directly at the game values through symbols gtDate_&lt;field&gt; (re-registered by the timer)
G.DATEF={'TimeYear','TimeMonth','TimeDay','CurrentHour'}
function G.regDateSyms(o)
  if not o then return end
  if G.st.dateSymObj==o then return end
  for _,n in ipairs(G.DATEF) do
    local p=UE.P(o,n)
    if p then pcall(unregisterSymbol,'gtDate_'..n) registerSymbol('gtDate_'..n, o+p.off, true) end
  end
  G.st.dateSymObj=o
end
function G.dateFreezeOn()
  local o=G.need(G.timeOwner(),'Game time not found (load save data first).','ゲーム内時間が見つかりません（セーブデータをロードしてください）。')
  local c={} for _,n in ipairs(G.DATEF) do c[n]=UE.get(o,n) end
  G.st.dateFix={o=o, v=c, prog=UE.get(o,'DayProgress')}
end
-- keep date and day progress; a change of a date value while frozen is an edit by the user and is adopted
function G.dateFreezeTask()
  local s=G.st.dateFix; if not s then return end
  local o=G.timeOwner('noscan'); if not o or o~=s.o then return end
  for _,n in ipairs(G.DATEF) do
    local v,p=UE.get(o,n)
    if v and s.v[n] and v~=s.v[n] then s.v[n]=v end
  end
  if s.prog then local v,p=UE.get(o,'DayProgress') if v and v~=s.prog then UE.wr(o+p.off,p,s.prog) end end
end
function G.mapUnlock()
  G.fresh()
  local k=0
  for _,s in ipairs(G.map('spot').list) do if UE.P(s,'PlayerVisited') and not UE.get(s,'PlayerVisited') then UE.set(s,'PlayerVisited',true) k=k+1 end end
  local d=0
  for _,o in ipairs(UE.instances(G.C.discovery)) do
    for _,n in ipairs({'IsDiscovered','Discovered','IsRevealed','Revealed','IsFound','IsVisible','IsActive'}) do
      local v,p=UE.get(o,n) if p and p.t=='BoolProperty' and not v then UE.wr(o+p.off,p,true) d=d+1 end
    end
  end
  GT.msg(('Map: %d cities marked visited, %d discovery flags set.'):format(k,d),('マップ: %d都市を訪問済み、発見地点フラグ%d件を設定しました。'):format(k,d))
end
G.COUNTRYFIELDS = {
  {'UsableStoredScienceTotal|ScienceTotal','Science','科学'},{'HumanitiesTotal','Humanities','人文'},{'ObelisqueInvestSetValue|ObelisqueInvest','Obelisk investment','オベリスク投資'},{'dv_Division','Division','分断'},
  {'dv_Tolerance','Tolerance','寛容'},{'dv_Hate','Hatred','憎悪'},
}
function G.listCountry(mr)
  G.fresh()
  local c=G.needCountry()
  G.rows(mr, 5, c, G.COUNTRYFIELDS)
end
-- generic DataTable editor
function G.listDataTable(mr)
  G.fresh()
  local names=UE.dataTableNames()
  while true do
    local i=UI.select(T('DataTable editor: table','データテーブル編集：表を選択'), names, {})
    if not i or i=='back' then return end
    local dt=UE.dataTable(names[i]) or GT.err('DataTable could not be read.','データテーブルを読み取れません。')
    local rn={} for k,r in ipairs(dt.rows) do rn[k]=r.name end
    local sel=UI.select(T('DataTable editor: rows (several allowed)','データテーブル編集：行を選択（複数可）')..' - '..names[i], rn, {multi=true, back=true})
    if sel==nil then return end
    if sel~='back' then
      for _,j in ipairs(sel) do
        local row=dt.rows[j]
        local h=GT.addHdr(mr, names[i]..' / '..row.name, names[i]..' / '..row.name)
        for _,p in ipairs(UE.allInfo(dt.struct)) do
          if p.t=='StrProperty' then GT.addHdr(h, p.cname..' = '..UE.fstr(row.ptr+p.off), p.cname..' = '..UE.fstr(row.ptr+p.off))
          else GT.addVal(h, p.cname, p.cname, ('%X'):format(row.ptr+p.off+((p.t=='BoolProperty' and p.bOff) or 0)), p) end
        end
      end
      return
    end
  end
end
-- generic object browser (game Blueprint classes only)
function G.listObject(mr)
  G.fresh()
  local cl={}
  for name,ents in pairs(UE.byName) do
    if name:sub(-2)=='_C' then
      for _,e in ipairs(ents) do
        local pth=UE.path(e.cls)
        if pth:find('/Game/_MyContents',1,true) then
          local n=0 for _,o in ipairs(e.objs) do if not UE.isCDO(o) then n=n+1 end end
          if n&gt;0 then cl[#cl+1]={name=name, cls=e.cls, n=n} end
          break
        end
      end
    end
  end
  table.sort(cl,function(a,b) return a.name&lt;b.name end)
  local names={} for k,c in ipairs(cl) do names[k]=('%s   (%d)'):format(c.name,c.n) end
  while true do
    local i=UI.select(T('Object browser: class','オブジェクト閲覧：クラスを選択'), names, {})
    if not i or i=='back' then return end
    local objs={} for _,o in ipairs(UE.byCls[cl[i].cls]) do if not UE.isCDO(o) then objs[#objs+1]=o end end
    local on={}
    for k,o in ipairs(objs) do
      local extra=UE.get(o,G.IDNAMES) or ''
      if UE.P(o,'FirstNameID|FirstName') then extra=G.unitName(o) end
      on[k]=('%s   %s'):format(extra, UE.objName(o) or '?')
    end
    local sel=UI.select(T('Object browser: objects (several allowed)','オブジェクト閲覧：オブジェクトを選択（複数可）')..' - '..cl[i].name, on, {multi=true, back=true})
    if sel==nil then return end
    if sel~='back' then
      for _,j in ipairs(sel) do
        local o=objs[j]
        local h=GT.addHdr(mr, on[j], on[j])
        for _,p in ipairs(UE.allInfo(cl[i].cls)) do
          if p.t=='StrProperty' then GT.addHdr(h, p.cname..' = '..UE.fstr(o+p.off), p.cname..' = '..UE.fstr(o+p.off))
          else GT.addVal(h, p.cname, p.cname, ('%X'):format(o+p.off+((p.t=='BoolProperty' and p.bOff) or 0)), p) end
        end
      end
      return
    end
  end
end

----------------------------------------------------------------- diagnostics
function G.ueCheck()
  GT.openLuaEngine(true)
  UE.need() UE.scan(true)
  local L=UE.L
  print(('[%s] FNamePool=%X blocks+%X lenShift=%d  GUObjectArray.Objects=%X items=%d'):format(GT.TAG,L.pool,L.blocksOff,L.lenShift,L.objs,UE.scanN))
  print(('[%s] UStruct super+%X childProps+%X  FField next+%X name+%X  FProperty dim+%X size+%X offset+%X sub+%X'):format(GT.TAG,L.super,L.childProps,L.fNext,L.fName,L.pDim,L.pSize,L.pOff,L.pBase))
  for k,c in pairs(G.C) do print(('[%s]   %-10s %-28s instances=%d'):format(GT.TAG,k,c,#UE.instances(c))) end
  local u=G.player()
  print(('[%s] player=%s  faction=%s  country=%s  army=%s'):format(GT.TAG, u and G.unitName(u) or '-', tostring(G.playerPower() and G.powerName(G.playerPower())),
        tostring(G.playerCountry() and G.id(G.playerCountry())), tostring(G.playerArmy() and G.id(G.playerArmy()))))
  for _,s in ipairs({'fmMalloc','processEvent'}) do print(('[%s] %s = %s'):format(GT.TAG, s, GT.A[s] and hx(GT.A[s]) or 'NOT FOUND')) end
end
-- bytecode of a Blueprint function: the UStruct::Script array (located after ChildProperties; validated by the last
-- byte EX_EndOfScript = 0x53).  Lists, in order, every property / function the code refers to (8-byte pointers in the
-- bytecode), with the opcode byte that precedes it.  Used to find where damage is applied and what it checks.
function G.scriptOf(f)
  for _,off in ipairs({UE.L.childProps+0x10, UE.L.childProps+0x18, UE.L.childProps+0x20, UE.L.childProps+0x8}) do
    local d,n,mx=UE.arr(f+off)
    if d~=0 and n&gt;0 and n&lt;=mx and n&lt;0x200000 then
      local last=readBytes(d+n-1,1,false)
      if last==0x53 then return d,n,off end
    end
  end
end
----------------------------------------------------------------- live check of an object pointer
-- true while &lt;o&gt; is still the object registered at its own index in GUObjectArray (destroyed objects are never written)
function UE.isLive(o)
  if not o or o==0 then return false end
  local i=ri(o+0xC); if not i or i&lt;0 or i&gt;=(UE.numObjects() or 0) then return false end
  return UE.objAt(i)==o
end

----------------------------------------------------------------- selected person / city (editors)
-- objects shown on screen: live widgets (WidgetBlueprintGeneratedClass) that refer to an object of class &lt;cname&gt;
-- by an object reference, or by an ID string member (&lt;idName&gt;, e.g. UnitID / SpotID); newest widget first
function G.shownObjects(kind, cname, idName)
  UE.need() UE.scan(true)
  G.st.wfields=G.st.wfields or {}
  local hits={}
  local lid=idName:lower()
  for cl,objs in pairs(UE.byCls) do
    if UE.className(cl)=='WidgetBlueprintGeneratedClass' then
      local ck=cname..'|'..('%X'):format(cl)
      local fl=G.st.wfields[ck]
      if not fl then
        fl={}
        for _,f in ipairs(UE.fields(cl)) do
          local p=UE.info(f)
          if (p.t=='ObjectProperty' and p.cls and UE.objName(p.cls)==cname) or (p.t=='StrProperty' and p.cname:lower():gsub(' ',''):sub(-#lid)==lid) then fl[#fl+1]=p end
        end
        G.st.wfields[ck]=fl
      end
      if #fl&gt;0 then
        for _,w in ipairs(objs) do
          if not UE.isCDO(w) then
            local vis=UE.get(w,'Visibility')
            if vis~=1 and vis~=2 then
              for _,p in ipairs(fl) do
                local t
                if p.t=='ObjectProperty' then t=rq(w+p.off) if t and t~=0 and not G.valid(t,cname) then t=nil end
                else t=G.byId(kind, UE.fstr(w+p.off)) end
                if t and t~=0 then hits[#hits+1]={o=t, idx=ri(w+0xC) or 0} end
              end
            end
          end
        end
      end
    end
  end
  table.sort(hits, function(a,b) return a.idx&gt;b.idx end)
  local r,seen={},{}
  for _,h in ipairs(hits) do if not seen[h.o] then seen[h.o]=true r[#r+1]=h.o end end
  return r
end
G.EDITOR={unit=524, spot=324}
function G.refreshEditor(kind)
  local m=getAddressList().getMemoryRecordByID(G.EDITOR[kind])
  if m and m.Active then GT.clearChildren(m) if kind=='unit' then G.editUnit(m) else G.editSpot(m) end end
end
local function chooseShown(kind, list, title, nameFn)
  if #list==0 then return nil end
  if #list==1 then return list[1] end
  local names={} for i,o in ipairs(list) do names[i]=nameFn(o) end
  local k=UI.select(title, names, {})
  if not k or k=='back' then return nil end
  return list[k]
end
function G.setSel(kind, o)
  G.select(kind, o)
  local nm=kind=='unit' and G.unitName(o) or G.spotName(G.id(o))
  GT.msg('Selected: '..nm,'選択しました: '..nm)
  G.refreshEditor(kind)
end
function G.selUnitShown()
  G.fresh()
  local l=G.shownObjects('unit', G.C.unit, 'UnitID')
  local u=chooseShown('unit', l, T('Persons on screen (newest window first)','画面に表示中の人物（新しく開いた画面の順）'), G.unitName)
  if not u then GT.msg('No person found on screen. Open the person screen first.','画面に表示中の人物が見つかりません。人物画面を開いてから実行してください。') return end
  G.setSel('unit', u)
end
function G.selUnitSearch()
  G.fresh()
  local u=G.pickUnit(T('Select a person (type to filter)','人物を選択（入力で絞り込み）'))
  if not u or u=='back' then return end
  G.setSel('unit', u)
end
function G.selUnitPlayer() G.fresh() G.setSel('unit', G.needPlayer()) end
function G.selSpotShown()
  G.fresh()
  local l=G.shownObjects('spot', G.C.spot, 'SpotID')
  local s=chooseShown('spot', l, T('Cities on screen (newest window first)','画面に表示中の都市（新しく開いた画面の順）'), function(o) return G.spotName(G.id(o)) end)
  if not s then GT.msg('No city found on screen. Open the city screen first.','画面に表示中の都市が見つかりません。都市画面を開いてから実行してください。') return end
  G.setSel('spot', s)
end
function G.selSpotStay()
  G.fresh()
  local u=G.needPlayer()
  local id=UE.get(u,'StayingSpotID_LastCache')
  local s=G.byId('spot', id)
  if not s then GT.msg('You are not staying in a city.','現在どの都市にも滞在していません。') return end
  G.setSel('spot', s)
end
function G.selSpotSearch()
  G.fresh()
  local s=G.pickSpot(T('Select a city (type to filter)','都市を選択（入力で絞り込み）'))
  if not s or s=='back' then return end
  G.setSel('spot', s)
end
local function selUnit()
  local u=G.selValid('unit')
  if not u then GT.err('No person selected. Use one of the "Select" items first.','人物が選択されていません。先に「選択」のいずれかを実行してください。') end
  return u
end
local function selSpot()
  local s=G.selValid('spot')
  if not s then GT.err('No city selected. Use one of the "Select" items first.','都市が選択されていません。先に「選択」のいずれかを実行してください。') end
  return s
end
-- editor rows follow the selection through pointer slot 2 (person) / 3 (city)
G.UNITCAT={
  {{'Basic','基本'}, {{'Money','Money','所持金'},{'Influence','Influence','影響度'},{'Renown','Renown','名声'},{'Rank','Rank','ランク'},{'dv_Tier','Tier','階級'},
                     {'dv_Age','Age','年齢'},{'BirthYear','Birth year','生年'},{'FocusPoint','Focus points','フォーカスポイント'},{'Tolerance','Tolerance','寛容'},{'dv_hate','Hatred','憎悪'}}},
  {{'Condition','状態'}, {{'HPCurrent','HP','HP'},{'dv_HPMax','Max HP','最大HP'},{'CurrentHealth','Health','健康'},{'CurrentSanity','Sanity','正気'},{'IsPregnant','Pregnant','妊娠中'},{'dv_IsDead','Dead','死亡'}}},
  {{'Ability EXP','能力経験値'}, {{'LeadershipExp','Leadership','統率'},{'PoliticsExp','Politics','政治'},{'InteligenceExp','Intelligence','知力'},
                                 {'BattleExp','Battle','武力'},{'IntuitionExp','Intuition','直感'},{'CharmExp','Charm','魅力'}}},
  {{'Ability level (cache; recalculated by the game)','能力レベル（キャッシュ・ゲームが再計算）'}, {{'LeadershipLV_LastCache','Leadership','統率'},{'PoliticsLV_LastCache','Politics','政治'},{'InteligenceLV_LastCache','Intelligence','知力'},
                                 {'BattleLV_LastCache','Battle','武力'},{'IntuitionLV_LastCache','Intuition','直感'},{'CharmLV_LastCache','Charm','魅力'}}},
}
function G.editUnit(mr)
  G.fresh()
  local u=G.selValid('unit')
  if not u then u=G.player() if u then G.select('unit',u) end end
  if not u then GT.addHdr(mr,'(no person selected)','（人物が選択されていません）') return end
  GT.addHdr(mr, 'Selected: '..G.unitName(u), '選択中: '..G.unitName(u))
  for _,c in ipairs(G.UNITCAT) do
    local h=GT.addHdr(mr, c[1][1], c[1][2])
    G.rows(h, 2, u, c[2])
  end
  local pid=G.id(G.player() or 0)
  local h=GT.addHdr(mr,'Relations','関係')
  if pid~='' and G.id(u)~=pid then
    local a,p=UE.addr(u,'UnitImpression')
    if a and p.key then local v=UE.mapFindStr(a,p,pid) if v then GT.addVal(h,'Impression of you','あなたへの好感度',('%X'):format(v),p.val) end end
    local me=G.player()
    a,p=UE.addr(me,'UnitImpression')
    if a and p.key then local v=UE.mapFindStr(a,p,G.id(u)) if v then GT.addVal(h,'Your impression of this person','あなたからこの人物への好感度',('%X'):format(v),p.val) end end
  end
end
G.SPOTCAT={
  {{'Resources','資源'}, {{'Population','Population','人口'},{'HumanResource|dv_HumanResource_Raw','Manpower','人的資源'},{'dv_HumanResourceMax','Max manpower','最大人的資源'},
                         {'FoodSurplus','Food surplus','食料余剰'},{'MoneyToInvestCommerce','Commerce investment','商業投資'},{'dv_IncomeToPower','Income to faction','勢力への収入'}}},
  {{'Development (stored values)','内政（基礎値）'}, {{'Commerce_Raw','Commerce','商業'},{'Agriculture_Raw','Agriculture','農業'},{'Loyalty_RawFix|Loyalty_Raw','Loyalty','忠誠'},
                         {'Safety_Raw','Safety','治安'},{'WarDevastation_Raw','War devastation','戦争荒廃'},{'Construction_Raw','Construction','建設力'}}},
  {{'Development (shown; recalculated daily)','内政（表示値・毎日再計算）'}, {{'Commerce_Final','Commerce','商業'},{'Agriculture_Final','Agriculture','農業'},{'Loyalty_Final','Loyalty','忠誠'},
                         {'Safety_Final','Safety','治安'},{'WarDevastation_Final','War devastation','戦争荒廃'},{'Construction_Final','Construction','建設力'}}},
  {{'Walls','城壁'}, {{'WallDefence','Wall durability','城壁耐久'},{'WallLevelToDefenceMulti','Durability per wall level','城壁レベルあたり耐久'}}},
  {{'State','状態'}, {{'InSiege','Under siege','包囲中'},{'PlayerVisited','Visited','訪問済み'},{'dv_hate','Hatred','憎悪'}}},
}
function G.editSpot(mr)
  G.fresh()
  local s=G.selValid('spot')
  if not s then GT.addHdr(mr,'(no city selected)','（都市が選択されていません）') return end
  local ow=G.spotOwners()[G.id(s)]
  GT.addHdr(mr, ('Selected: %s  |  %s'):format(G.spotName(G.id(s)), G.powerName(ow)), ('選択中: %s  |  %s'):format(G.spotName(G.id(s)), G.powerName(ow)))
  for _,c in ipairs(G.SPOTCAT) do
    local h=GT.addHdr(mr, c[1][1], c[1][2])
    G.rows(h, 3, s, c[2])
  end
  local shown = {}
  for _, category in ipairs(G.SPOTCAT) do
    for _, field in ipairs(category[2]) do
      local property = UE.P(s, field[1])
      if property then shown[property.off] = true end
    end
  end
  local extra = {}
  for _, field in ipairs(G.SPOTFIELDS) do
    local property = UE.P(s, field[1])
    if property and not shown[property.off] then extra[#extra+1] = field end
  end
  if #extra &gt; 0 then
    G.rows(GT.addHdr(mr, 'Other city parameters', 'その他の都市項目'), 3, s, extra)
  end
  local a,p=UE.addr(s,'Facility')
  if a and p.inner and p.inner.struct then
    local pid,plv=UE.PS(p.inner.struct,'StringID'),UE.PS(p.inner.struct,'CurrentLevel')
    local fh=GT.addHdr(mr,'Facilities (level)','施設（レベル）')
    local d,n=UE.arr(a)
    for i=0,n-1 do
      local nm=G.facilityName(UE.fstr(d+i*p.inner.size+pid.off))
      GT.addVal(fh, nm, nm, ('[[gtPtrs+18]+%X]+%X'):format(p.off, i*p.inner.size+plv.off), plv)
    end
  end
  local h=GT.addHdr(mr,'Impression','印象')
  local me,pw,c=G.player(),G.playerPower(),G.playerCountry()
  for _,t in ipairs({{'UnitImpression',me,'Impression of you','あなたへの印象'},{'PowerImpression',pw,'Impression of your faction','自勢力への印象'},{'CountryImpression',c,'Impression of your country','自国への印象'}}) do
    local ia,ip=UE.addr(s,t[1])
    if ia and ip.key and t[2] then local v=UE.mapFindStr(ia,ip,G.id(t[2])) if v then GT.addVal(h,t[3],t[4],('%X'):format(v),ip.val) end end
  end
end
-- actions on the selected person / city
function G.selRecruit() G.fresh() local u=selUnit() if UE.get(u,'IsPlayer') then GT.err('The player is selected.','プレイヤーが選択されています。') end G.doRecruit({u}) G.refreshEditor('unit') end
function G.selMarry() G.fresh() G.doMarry(selUnit()) end
function G.selImpression() G.fresh() local u=selUnit() if UE.get(u,'IsPlayer') then GT.err('The player is selected.','プレイヤーが選択されています。') end G.doImpression({u}) G.refreshEditor('unit') end
function G.selReveal() G.fresh() G.doReveal({selUnit()}) end
function G.selAbilities() G.fresh() G.maxAbilitiesFor(selUnit()) end
function G.selSeize() G.fresh() G.doSeize({selSpot()}) G.refreshEditor('spot') end
function G.selVacate() G.fresh() G.doVacate({selSpot()}) G.refreshEditor('spot') end
function G.selNormalize() G.fresh() local s=selSpot() G.normalizeSpot(s) GT.msg('Normalized: '..G.spotName(G.id(s)),'平常化しました: '..G.spotName(G.id(s))) end
function G.selFacilitiesMax() G.fresh() G.facilitiesMax({selSpot()}) G.refreshEditor('spot') end
function G.selSpotImpression() G.fresh() G.doSpotImpression({selSpot()}) G.refreshEditor('spot') end

----------------------------------------------------------------- 7. realtime battle
-- enemy one-hit: enemy Stat.Health kept at 1 or less (the next hit defeats them)
-- siege of an enemy spot: BattleActor_C (battle manager; battle diagnosis: IsSpotBattle, AttackedSpot, SpotCivDefence=100)
--   resistance of the residents = BattleActor_C.SpotCivDefence (game text: "住民の抵抗値が0になるとこの土地は降伏する")
--   walls = AttackedSpot.WallDefence and the city wall actors of the battle
--   stockpiled food = AttackedSpot.FoodSurplus (game text: "食料余剰がないと住民抵抗が下って…")
-- applied only when the attacked spot is not owned by your faction
function G.enemySiegeWeaken()
  local b=G.st.bat; if not b or not b.ba then return end
  local my=G.playerPower('noscan') local myId=my and G.id(my)
  for _,ba in ipairs(b.ba) do
    if liveActor(ba) and UE.get(ba,'IsSpotBattle') then
      local sp=UE.get(ba,'AttackedSpot')
      if sp and sp~=0 and G.valid(sp,G.C.spot) then
        local owner=UE.get(sp,'BelongingPowerID_LastCache')
        local mine=myId and owner==myId
        if not mine and my and UE.strArrHas(my,'MemberSpotIDs',G.id(sp)) then mine=true end
        if not mine then
          -- kept at 1 (0 ends the battle at once)
          local v,p=UE.get(ba,'SpotCivDefence') if v and v&gt;1 then UE.wr(ba+p.off,p,1.0) end
          v,p=UE.get(sp,'WallDefence') if v and v&gt;1 then UE.wr(sp+p.off,p,1.0) end
          v,p=UE.get(sp,'FoodSurplus') if v and v&gt;1 then UE.wr(sp+p.off,p,1.0) end
          for _,w in ipairs(b.walls or {}) do
            if liveActor(w) then local a,x=G.repHealth(w) if a and x and x&gt;1 then writeDouble(a,1.0) end end
          end
        end
      end
    end
  end
end
function G.taskEnemyOneHit()
  pcall(G.enemySiegeWeaken)
  for _,x in ipairs(G.battleEnemies()) do
    local a,v=G.repHealth(x)
    if a and v and v&gt;1 then writeDouble(a, 1.0) end
    local sa,sp=G.statAddr(x,'Stat.Health')
    if sa then local sv=UE.rd(sa,sp) if sv and sv&gt;1 then UE.wr(sa,sp,1.0) end end
  end
end
local function shortCls(o) return ((UE.className(o) or '?'):gsub('^BattleActor_',''):gsub('_C$','')) end
function G.listBattle(mr)
  local bp=G.battlePawn()
  if not bp then GT.addHdr(mr,'(not in a realtime battle)','（リアルタイム戦闘中ではありません）') return end
  local function block(title_en, title_ja, list)
    local h=GT.addHdr(mr, ('%s (%d)'):format(title_en,#list), ('%s（%d）'):format(title_ja,#list))
    for i,x in ipairs(list) do
      if i&gt;200 then break end
      local a,p=G.statAddr(x,'Stat.Health')
      if a then local nm=('%s #%d  HP'):format(shortCls(x), i) GT.addVal(h, nm, nm, ('%X'):format(a), p) end
    end
  end
  local a,p=G.statAddr(bp,'Stat.Health')
  if a then GT.addVal(mr,'You: HP','あなた：HP',('%X'):format(a),p) end
  local own={} for _,x in ipairs(G.battleOwnSoldiers()) do if UE.isLive(x) then own[#own+1]=x end end
  block('Your soldiers','自軍の兵士', own)
  block('Enemies','敵軍', G.battleEnemies())
end


----------------------------------------------------------------- fast battle protection (every 50 ms)
-- The normal task timer runs every 500 ms; several hits inside that time can take HP from full to 0.
-- While 1. Invincible or 2. Army invincible is on, a 50 ms timer keeps Stat.Health of the player / your soldiers full.
function G.startFast()
  if G.fastT then return end
  G.fastT=createTimer(nil,false) G.fastT.Interval=50
  G.fastT.OnTimer=function()
    if not GT.pid or not UE.ready or not (GT.featOn.invincible or GT.featOn.armyGod) then return end
    pcall(G.fastBattle)
  end
  G.fastT.Enabled=true
end
function G.fastBattle()
  local r=G.st.batR
  if not r or getTickCount()-r.t&gt;1000 then return end   -- lists are verified by the 500 ms tasks (G.battleActors)
  if GT.featOn.invincible and r.player and liveActor(r.player) then G.pawnFull(r.player) end
  if GT.featOn.armyGod then for _,x in ipairs(r.own) do if liveActor(x) then G.pawnFull(x) end end end
end

----------------------------------------------------------------- player HP guard in realtime battle (hardware write breakpoint)
-- Log of 16:19:41: the player's Stat.Health went from full to 0 between two 50 ms checks ("DEAD player ... lowest
-- seen=0.0") and the army surrendered ("撤退理由：リーダーが戦闘不能").  Restoring HP afterwards cannot stop that.
-- A CPU debug register (write breakpoint) on the Value of the player's Stat.Health runs the handler right after the
-- game's write instruction, before the game's next instruction reads HP; the handler puts HP back to Max.
-- Only the player pawn is guarded (the CPU has 4 debug registers).
function G.hpGuardSet(pawn)
  local g=G.st.hpg
  if g and g.pawn==pawn then return end
  G.hpGuardOff()
  if not pawn then return end
  local a=G.repHealth(pawn); if not a then return end
  if not debug_isDebugging() then
    local ok=pcall(debugProcess)
    if not ok or not debug_isDebugging() then
      if not G.st.hpgWarn then G.st.hpgWarn=true GT.msg('Debugger could not attach; battle HP guard is not available.','デバッガを接続できないため、戦闘中のHP保護（書き込み監視）は使えません。') end
      return
    end
  end
  local ok=pcall(debug_setBreakpoint, a, 8, bptWrite, bpmDebugRegister, function()
    -- write only while this is still the live player battle pawn
    if UE.isLive(pawn) and UE.className(pawn)=='BattleActor_Player_C' then
      local x,y=readDouble(a), readDouble(a+8)
      if x and y and y&gt;0 and y&lt;1e7 and x&lt;y then writeDouble(a, y) end
    end
    debug_continueFromBreakpoint(co_run)
    return 1
  end)
  if ok then G.st.hpg={a=a, pawn=pawn} end
end
function G.hpGuardOff()
  local g=G.st and G.st.hpg
  if g then pcall(debug_removeBreakpoint, g.a) G.st.hpg=nil end
end
function G.taskHpGuard()
  local bp=G.battlePawn()
  if not bp or not liveActor(bp) or UE.className(bp)~='BattleActor_Player_C' then G.hpGuardOff() return end
  G.hpGuardSet(bp)
end


----------------------------------------------------------------- damage 0 for the player side (Blueprint patch)
-- BP_AI_Main_C.HandleAIDamageTaken is the damage handler of every realtime-battle pawn.  Its bytecode starts with
--   EX_JumpIfNot(07) &lt;target&gt; EX_InstanceVariable(01) &lt;AfterDamageInvincible&gt;
-- i.e. "if AfterDamageInvincible then (print and) return, else apply the damage".
-- The patch replaces the property pointer of that condition with IsPlayerSide (same opcode, same length):
-- every pawn with IsPlayerSide = true (you, your army and allied armies) returns before any damage is applied.
-- The patch is applied only when the exact byte pattern is found and the true branch ends with EX_Return
-- (04 0B); it is reverted when 1. Invincible and 2. Army invincible are both off.
local function u32(b,i) return b[i]+b[i+1]*256+b[i+2]*65536+b[i+3]*16777216 end
local function q64(b,i) local v=0 for k=7,0,-1 do v=v*256+b[i+k] end return v end
function G.dmgPatchApply()
  local st=G.st
  if st.dmgp then return true end
  if st.dmgpFail and getTickCount()-st.dmgpFail&lt;10000 then return false end
  local function fail(en, ja) st.dmgpFail=getTickCount() if not st.dmgpMsg then st.dmgpMsg=true GT.msg(en,ja) end return false end
  local base=UE.findClass('BP_AI_Main_C'); if not base then st.dmgpFail=getTickCount() return false end
  local root=UE.findClass('MyBattleActorPawn_Root_C'); if not root then st.dmgpFail=getTickCount() return false end
  local fn=UE.childObject('Function','HandleAIDamageTaken',base)
  if not fn then return fail('HandleAIDamageTaken not found.','HandleAIDamageTaken が見つかりません。') end
  local pA=UE.PS(base,'AfterDamageInvincible'); local pS=UE.PS(root,'IsPlayerSide')
  if not pA or not pS then return fail('AfterDamageInvincible / IsPlayerSide not found.','AfterDamageInvincible／IsPlayerSide が見つかりません。') end
  local d,n=G.scriptOf(fn); if not d then return fail('Bytecode of HandleAIDamageTaken not found.','HandleAIDamageTaken の命令列が見つかりません。') end
  local lim=math.min(n,0x200)
  local b=readBytes(d,lim,true); if not b then return fail('Bytecode not readable.','命令列を読めません。') end
  for i=1,lim-13 do
    if b[i]==0x07 and b[i+5]==0x01 and q64(b,i+6)==pS.f and b[i+14]==0x04 then   -- already patched (e.g. before a re-attach)
      local ro=st.dmgpR
      if not ro then
        local ksl=UE.findClass('KismetSystemLibrary')
        local ps=ksl and UE.childObject('Function','PrintString',ksl)
        ro=ps and {0x68, ps &amp; 0xFF} or nil            -- the 2nd byte is the low byte of the PrintString pointer
      end
      st.dmgp={at=d+(i-1)+6, orig=pA.f, new=pS.f, rAt=d+(i-1)+14, rOrig=ro} return true
    end
    if b[i]==0x07 and b[i+5]==0x01 and q64(b,i+6)==pA.f then
      -- the true branch (condition met) is the code right after the condition; game data of 2026-10-11:
      -- it calls PrintString (EX_CallMath 0x68) and ends with EX_EndFunctionParms + EX_PopExecutionFlow (16 4D) at the
      -- jump target, i.e. it applies no damage.  Its first two bytes are replaced by EX_Return EX_Nothing (04 0B), so
      -- the function returns at once when the condition is met.
      local tgt=u32(b,i+1)
      local tb=readBytes(d+tgt-2,2,true)
      local okEnd=tb and ((tb[1]==0x04 and tb[2]==0x0B) or tb[2]==0x4D)
      if not okEnd or b[i+14]~=0x68 or tgt&lt;=(i-1)+16 then
        return fail(('Unexpected true branch (target %X: %s, first %02X).'):format(tgt, tb and ('%02X %02X'):format(tb[1],tb[2]) or '?', b[i+14] or 0),
                    ('想定外の分岐です（行き先 %X：%s、先頭 %02X）。パッチしません。'):format(tgt, tb and ('%02X %02X'):format(tb[1],tb[2]) or '?', b[i+14] or 0))
      end
      local at=d+(i-1)+6
      local rAt=d+(i-1)+14
      local rOrig={b[i+14], b[i+15]}
      writeBytes(rAt, 0x04, 0x0B)
      writeQword(at, pS.f)
      st.dmgp={at=at, orig=pA.f, new=pS.f, rAt=rAt, rOrig=rOrig}
      st.dmgpR=rOrig
      GT.msg('Damage 0 for the player side is active (HandleAIDamageTaken patched).','味方側のダメージ0を有効にしました（HandleAIDamageTaken を書き換え）。')
      return true
    end
  end
  return fail('Condition pattern not found in HandleAIDamageTaken.','HandleAIDamageTaken に想定の条件が見つかりません。パッチしません。')
end
function G.dmgPatchRevert()
  local p=G.st and G.st.dmgp
  if p then
    if readQword(p.at)==p.new then writeQword(p.at, p.orig) end
    if p.rAt and p.rOrig then local cur=readBytes(p.rAt,2,true) if cur and cur[1]==0x04 and cur[2]==0x0B then writeBytes(p.rAt, p.rOrig[1], p.rOrig[2]) end end
    G.st.dmgp=nil
  end
end
function G.taskDmgPatch()
  if G.battlePawn() then G.dmgPatchApply() end
end
function G.dmgPatchOff()
  if not (GT.featOn.invincible or GT.featOn.armyGod) then G.dmgPatchRevert() end
end

GT.SPOTS = {  -- index = spot ID (DT_SpotList order): {ja, en, type}
  {'ドーラ','Dora','Village'}, {'アラバ','Araba','Village'}, {'ガール','Gaal','Village'}, {'ニシュール','Nishule','Village'}, {'オレイイレイの要塞','Oreillei Fortress','Fort'}, {'パタパ','Patapa','Village'}, {'パラフィト','Parafit','Village'}, {'シドヌ','Sidon','Village'}, {'ダーファ','Darfa','Village'}, {'スニス','Sunis','Village'}, {'バルブール','Balbul','Village'}, {'カルド','Kald','Village'}, {'リルトゥ','Rilthu','Village'}, {'セヒ','Sehi','City'}, {'堕落の都ルテ','Corrupt City Lute','City'}, {'森の街アルマナ','Forest City Almana','City'}, {'神聖都市ピレト','Holy City Pyret','City'}, {'リデル','Liddell','City'}, {'古ルシュ','Old Leuche','City'}, {'交易都市エンタル','Trade City Ental','City'}, {'灯台の街メリ','Lighthouse City Meri','City'}, {'ワルサザード城塞','Ouarzazate Citadel','Fort'}, {'名を奪われた街','The Nameless City','City'}, {'カディーシュ城塞','Kadish Citadel','Fort'}, {'キア城','Kia Castle','Fort'}, {'審問府','Inquisition','City'}, {'自由都市ウリア','Free City Uria','City'}, {'ピカデの城塞都市','Fortress City Picade','City'}, {'メギナ','Megina','Village'}, {'リゲレシュ','Rigeresh','Village'}, {'旧ウリア関','Old Uria Gate','Fort'}, {'アマ','Ama','Village'}, {'リマルナ','Rimalna','Village'}, {'フルラ','Furla','Village'}, {'快楽の街エシュリ','Hedonic City Eshri','City'}, {'ギムド','Gimd','Village'}, {'港町グリカタ','Port Gricata','City'}, {'ウ・レイ列要塞','U-Lei Fortress','Fort'},
}
GT.ITEMS = {  -- {ItemTypeID(ja), en, category}
  {'レザーアーマー','Leather Armor','Body'},
  {'チェインメイルアーマー','Chainmail Armor','Body'},
  {'ハーフプレイト','Half-Plate Armor','Body'},
  {'プレイトメイルアーマー','Plate Mail Armor','Body'},
  {'英雄の鎧','Armor of Erol','Body'},
  {'聖戦士の鎧','Crusader\'s Armor','Body'},
  {'狩猟用の弓','Hunting Bow','Bow'},
  {'短弓','Short Bow','Bow'},
  {'長弓','Longbow','Bow'},
  {'遊牧民の弓','Nomad\'s Bow','Bow'},
  {'セヒの弓','Sehi\'s Bow','Bow'},
  {'剛腕の弓','Greatbow of Strength','Bow'},
  {'戦神弓','Ares\' Bow','Bow'},
  {'マスケット','Musket','Gun'},
  {'小型砲','Small Cannon','Canon'},
  {'中型砲','Medium Cannon','Canon'},
  {'大型砲','Large Cannon','Canon'},
  {'鱗','Scale','Junk'},
  {'木の実','Wild Fruit','Junk'},
  {'薪','Firewood','Junk'},
  {'動物の骨','Animal Bone','Junk'},
  {'鳥の羽','Feather','Junk'},
  {'貝殻','Shell','Junk'},
  {'釘','Nail','Junk'},
  {'鉄くず','Scrap Iron','Junk'},
  {'黒曜石の欠片','Obsidian Fragment','Junk'},
  {'銅鉱石の欠片','Copper Ore Fragment','Junk'},
  {'黄銅鉱の欠片','Copper Pyrite Fragment','Junk'},
  {'動物の角','Animal Horn','Junk'},
  {'琥珀の欠片','Amber Fragment','Junk'},
  {'ガラス瓶','Glass Bottle','Junk'},
  {'絹のリボン','Silk Ribbon','Quest'},
  {'聖なる香油','Sacred Oil','Quest'},
  {'ネコ','Cat','Quest'},
  {'絹のショール','Silk Shawl','Quest'},
  {'ダガー','Dagger','OneHandArmy'},
  {'ショートソード','Short Sword','OneHandArmy'},
  {'カトラス','Cutlass','OneHandArmy'},
  {'エペ','Épée','OneHandArmy'},
  {'サーベル','Saber','OneHandArmy'},
  {'ショート･サーベル','Short Saber','OneHandArmy'},
  {'レイピア','Rapier','OneHandArmy'},
  {'エストック','Estoc','OneHandArmy'},
  {'フラムベルク','Flamberge','OneHandArmy'},
  {'シヴァの妖剣','Shiva\'s Cursed Blade','OneHandArmy'},
  {'白銀の針','Silver Needle','OneHandArmy'},
  {'オベリスクの謎1','Obelisk Key - Shadow','Quest'},
  {'オベリスクの謎2','Obelisk Key - Silence','Quest'},
  {'オベリスクの謎3','Obelisk Key - Time','Quest'},
  {'オベリスクの謎4','Obelisk Key - Solitude','Quest'},
  {'オベリスクの謎5','Obelisk Key - Water Mirror','Quest'},
  {'オベリスクの謎6','Obelisk Key - Ideal','Quest'},
  {'オベリスクの謎7','Obelisk Key - Existence','Quest'},
  {'破滅の魔剣','Doomblade','Quest'},
  {'玉璽','Imperial Seal','Quest'},
  {'木の盾','Wooden Shield','Shield'},
  {'革の盾','Leather Shield','Shield'},
  {'鉄の盾','Iron Shield','Shield'},
  {'鋼の盾','Steel Shield','Shield'},
  {'銀の盾','Silver Shield','Shield'},
  {'黒曜石の盾','Obsidian Shield','Shield'},
  {'龍鱗の盾','Dragonscale Shield','Shield'},
  {'獅子の盾','Lion Shield','Shield'},
  {'ロングソード','Longsword','TwoHandArmy'},
  {'ブロード･ソード','Broadsword','TwoHandArmy'},
  {'シミター','Scimitar','TwoHandArmy'},
  {'バスタード･ソード','Buster Sword','TwoHandArmy'},
  {'クレイモア','Claymore','TwoHandArmy'},
  {'炎虎半月刀','Inferno Moonfang','TwoHandArmy'},
  {'紫電刀','Stormflash','TwoHandArmy'},
  {'青竜偃月刀','Azure Wyrmbrand','TwoHandArmy'},
  {'聖騎士の剣','Paladin\'s Sword','TwoHandArmy'},
  {'ルーン・ブレード','Rune Blade','TwoHandArmy'},
  {'薬草','Herb','UseItem'},
  {'ライム・ジュース','Lime Juice','UseItem'},
  {'エリクサー','Elixir','UseItem'},
  {'アルティアの滝の地図','Map of Altea Falls','Map'},
  {'青の森の地図','Azure Forest Map','Map'},
  {'ミネルヴァの図書館の地図','Map of Minerva\'s Library','Map'},
  {'ドネアの糸の地図','Map of Donea\'s Thread','Map'},
  {'メグの涙の地図','Map of Meg\'s Tear','Map'},
  {'ルフスの翼の地図','Map of Rufus\' Wing','Map'},
  {'緑の小瓶','Green Vial','Quest'},
  {'ギルドへの手紙','Guild Letter','Quest'},
  {'貴族のガウン','Noble Gown','Quest'},
}
GT.GOODS = {  -- {ID(ja), en}  (DT_TradeGoodsList)
  {'ろうそく','Candle'}, {'アヘン','Opium'}, {'ガラス器','Glassware'}, {'チーズ','Cheese'}, {'ナツメヤシ','Dates'}, {'バター','Butter'}, {'メノウ','Agate'}, {'ラピスラズリ','Lapis Lazuli'}, {'乳製品（食料）','Dairy (Food)'}, {'乳酒','Kumis'}, {'偶像','Idol'}, {'写本','Manuscript'}, {'呪術具','Ritual Tool'}, {'塩','Salt'}, {'大理石','Marble'}, {'工具','Tools'}, {'木材','Timber'}, {'染料','Dye'}, {'根菜','Tuber'}, {'椰子の実','Coconut'}, {'椰子酒','Coconut Wine'}, {'樽','Barrel'}, {'武器','Weapons'}, {'毛皮','Pelt'}, {'毛織物','Wool Cloth'}, {'沈香','Agarwood'}, {'燻製肉','Smoked Meat'}, {'版画','Woodblock Print'}, {'珊瑚','Coral'}, {'琥珀','Amber'}, {'生地','Cloth'}, {'真珠','Pearl'}, {'砂糖','Sugar'}, {'磁器','Porcelain'}, {'穀類（食料）','Grains (Food)'}, {'紙','Paper'}, {'美術品','Artwork'}, {'芋','Potato'}, {'茶','Tea'}, {'葡萄酒','Wine'}, {'蜂蜜','Honey'}, {'角牙','Ivory'}, {'金','Gold'}, {'鉱石','Ore'}, {'銀細工','Silverware'}, {'陶器','Pottery'}, {'馬具','Harness'}, {'魚肉（食料）','Fish (Food)'}, {'鶏','Chicken'}, {'麝香','Musk'}, {'麦酒','Beer'}, {'黒檀','Ebony'},
}
GT.SOLDIERTYPES = {  -- {ID(ja), en}  (DT_SoldierTypeList)
  {'エルデライン弓兵','Erdelein Archers'}, {'エルデライン担夫','Erdelein Porters'}, {'エルデライン歩兵','Erdelein Infantry'}, {'エルデライン砲兵','Erdelein Artillery'}, {'エルデライン砲戦車','Erdelein Gun Tank'}, {'エルデライン銃兵','Erdelein Musketeers'}, {'エルデライン銃騎兵','Erdelein Pistoleers'}, {'エルデライン飛行戦艦','Erdelein Airship'}, {'エルデライン騎兵','Erdelein Cavalry'}, {'ルシュ弓兵','Leuche Archers'}, {'ルシュ担夫','Leuche Porters'}, {'ルシュ歩兵','Leuche Infantry'}, {'ルシュ砲兵','Leuche Artillery'}, {'ルシュ砲戦車','Leuche Gun Tank'}, {'ルシュ銃兵','Leuche Musketeers'}, {'ルシュ銃騎兵','Leuche Pistoleers'}, {'ルシュ飛行戦艦','Leuche Airship'}, {'ルシュ騎兵','Leuche Cavalry'}, {'遊牧弓兵','Nomad Archers'}, {'遊牧担夫','Nomad Porters'}, {'遊牧歩兵','Nomad Infantry'}, {'遊牧砲兵','Nomad Artillery'}, {'遊牧砲戦車','Nomad Gun Tank'}, {'遊牧銃兵','Nomad Musketeers'}, {'遊牧銃騎兵','Nomad Pistoleers'}, {'遊牧飛行戦艦','Nomad Airship'}, {'遊牧騎兵','Nomad Cavalry'},
}
GT.FACILITIES = {  -- {ID(ja), en}  (DT_FacilityList)
  {'オベリスクの小箱','Obelisk Casket'}, {'オベリスクの箱','Obelisk Box'}, {'オベリスク研究施設','Obelisk Research Facility'}, {'公共広場','Public Square'}, {'兵舎','Barracks'}, {'図書館','Library'}, {'城壁','Castle Walls'}, {'大学','University'}, {'工房','Workshop'}, {'市場','Market'}, {'水車','Waterwheel'}, {'水道','Aqueduct'}, {'灌漑','Irrigation'}, {'穀物倉','Granary'}, {'議会','Council Hall'}, {'風車','Windmill'},
}
GT.TECHS = {  -- {ID(ja), en}  (DT_TechnologyTypes)
  {'三圃制','Three-Field System'}, {'信用取引','Credit Trading'}, {'印刷','Printing'}, {'同業者組合','Guild'}, {'四輪作法','Four-Wheel Production'}, {'堡塁','Bastion'}, {'大砲','Cannon'}, {'奴隷制','Slavery'}, {'官僚制','Bureaucracy'}, {'定期市','Market Stalls'}, {'徴兵制度','Conscription'}, {'教育','Education'}, {'有輪鋤','Wheeled Plow'}, {'機械','Machinery'}, {'民主制','Democracy'}, {'水車','Waterwheel'}, {'治水','Flood Control'}, {'灌漑','Irrigation'}, {'火薬','Gunpowder'}, {'熱機関','Thermal Engine'}, {'独立自営農民','Independent Farms'}, {'貨幣経済の普及','Monetary Economy'}, {'輪作','Crop Rotation'}, {'農奴制','Serfdom'}, {'銀行制度','Banking System'}, {'銃','Gun'}, {'鋳鉄','Cast Iron'}, {'風車','Windmill'}, {'飛行戦艦','Aerial Battleship'}, {'飛行技術','Flight Technology'}, {'騎乗戦術','Mounted Tactics'},
}
GT.BSKILLS = {  -- {ID(ja), en}  (DT_BattleSkill)
  {'まきびし','Caltrops'}, {'兵站破壊','Supply Line Sabotage'}, {'回復','Recovery'}, {'専守防衛A','Defensive Stance A'}, {'専守防衛S','Defensive Stance S'}, {'弓三連射','Triple Bow Shot'}, {'応急修理','Emergency Repairs'}, {'急速射撃','Rapid Fire'}, {'急速装填','Rapid Reload'}, {'挑発','Taunt'}, {'民衆鼓舞','Inspire the People'}, {'水脈汚染','Water Supply Contamination'}, {'決死攻撃D','Desperate Assault D'}, {'決死攻撃M','Desperate Assault M'}, {'決死攻撃S','Desperate Assault S'}, {'煙幕','Smoke Screen'}, {'相打ち覚悟','All-or-Nothing Strike'}, {'砲弾回避','Shell Evasion'}, {'破城槌','Battering Ram'}, {'神速','Swift March'}, {'精密射撃','Precision Shot'}, {'精密砲撃','Precision Bombardment'}, {'鉄壁','Iron Wall'}, {'鉄砲三連射','Triple Gun Volley'}, {'防御態勢','Defensive Formation'}, {'食料強奪','Food Raid'}, {'騎兵突撃','Cavalry Charge'}, {'騎馬威嚇','Cavalry Intimidation'}, {'鼓舞','Rally'},
}

GT.FIRSTNAMES = {  -- row name -&gt; {ja, en}  (DT_FirstNameList)
  ['10']={'オースティン','Austin'}, ['100']={'クエンティン','Quentin'}, ['1000']={'ツキオリ','Tsukiori'}, ['1001']={'ツキヨ','Tsukiyo'}, ['1002']={'ツユリ','Tsuyuri'}, ['1003']={'テマリ','Temari'}, ['1004']={'テムナ','Temuna'}, ['1005']={'トウカ','Toka'},
  ['1006']={'トモエ','Tomoe'}, ['1007']={'トルシア','Torsia'}, ['1008']={'ナギハ','Nagiha'}, ['1009']={'ナツメリ','Natsumeri'}, ['101']={'ラルフ','Ralph'}, ['1010']={'ナナミ','Nanami'}, ['1011']={'ナリンファ','Narinfa'}, ['1012']={'ニナエ','Ninae'},
  ['1013']={'ネネカ','Neneka'}, ['1014']={'ネフェルン','Nepheren'}, ['1015']={'ネムリ','Nemuri'}, ['1016']={'ノハナ','Nohana'}, ['1017']={'ノリエ','Norie'}, ['1018']={'ハヅキ','Hazuki'}, ['1019']={'ハナエ','Hanae'}, ['102']={'レイモンド','Raymond'},
  ['1020']={'ハルミナ','Harumina'}, ['1021']={'ヒカリ','Hikari'}, ['1022']={'ヒグレ','Higure'}, ['1023']={'ヒマリ','Himari'}, ['1024']={'ファーナ','Farna'}, ['1025']={'フタエ','Futae'}, ['1026']={'ヘイリン','Heilin'}, ['1027']={'ホトリ','Hotori'},
  ['1028']={'ホノスズ','Honosuzu'}, ['1029']={'マイリ','Mairi'}, ['103']={'リード','Reed'}, ['1030']={'マヤ','Maya'}, ['1031']={'マリエ','Marie'}, ['1032']={'ミオリ','Miori'}, ['1033']={'ミコト','Mikoto'}, ['1034']={'ミズハ','Mizuha'},
  ['1035']={'ミラン','Milan'}, ['1036']={'ムツハ','Mutsuha'}, ['1037']={'メイリンファ','Meilinfa'}, ['1038']={'メキ','Meki'}, ['1039']={'メルリゲ','Merlige'}, ['104']={'リチャード','Richard'}, ['1040']={'モトハ','Motohа'}, ['1041']={'モナカ','Monaka'},
  ['1042']={'ヤイリ','Yairi'}, ['1043']={'ヤエナ','Yaena'}, ['1044']={'ヤシロナ','Yashirona'}, ['1045']={'ヤチヨ','Yachiyo'}, ['1046']={'ユエラン','Yuenlan'}, ['1047']={'ユカリ','Yukari'}, ['1048']={'ユズハ','Yuzuha'}, ['1049']={'ユメル','Yumeru'},
  ['105']={'ライリー','Ryleigh'}, ['1050']={'ユラ','Yura'}, ['1051']={'ユリカ','Yurika'}, ['1052']={'ユンフェ','Yunfe'}, ['1053']={'ヨイミ','Yoimi'}, ['1054']={'ヨツハ','Yotsuha'}, ['1055']={'ラルナ','Larna'}, ['1056']={'ラン','Ran'},
  ['1057']={'ランカ','Ranka'}, ['1058']={'リオ','Rio'}, ['1059']={'リシュラ','Rishura'}, ['106']={'ロバート','Robert'}, ['1060']={'リツカ','Ritsuka'}, ['1061']={'リノハ','Rinoha'}, ['1062']={'リュシカ','Ryushika'}, ['1063']={'リュメイ','Ryumei'},
  ['1064']={'リヨナ','Riona'}, ['1065']={'リリナ','Ririna'}, ['1066']={'リンメイ','Rinmei'}, ['1067']={'ルリエ','Rurie'}, ['1068']={'レイカ','Reika'}, ['1069']={'レンリ','Renri'}, ['107']={'ロジャー','Roger'}, ['1070']={'ロウリン','Rowlin'},
  ['1071']={'エレイナ','Elaina'}, ['1072']={'シェリーア','Sherria'}, ['1073']={'ファビアン','Fabian'}, ['1074']={'ハンス','Hans'}, ['1075']={'ゲシュミルト','Geschmilt'}, ['1076']={'オーデア','Odea'}, ['1077']={'ザウルベック','Zaurbek'}, ['1078']={'ホーリア','Holia'},
  ['1079']={'ベルン','Bern'}, ['108']={'ライアン','Ryan'}, ['1080']={'アメリア','Amelia'}, ['1081']={'ルーク','Luke'}, ['1082']={'ヴァルトマール','Waldmar'}, ['1083']={'エステラ','Estella'}, ['1084']={'ガストロプ','Gastrop'}, ['1085']={'エレイーザ','Eleyza'},
  ['1086']={'ボルベーゼ','Borvese'}, ['1087']={'グラハム','Graham'}, ['1088']={'アデル','Adel'}, ['1089']={'ライレア','Lailea'}, ['109']={'スコット','Scott'}, ['1090']={'エド','Ed'}, ['1091']={'ビーラ','Beira'}, ['1092']={'ドイア','Doia'},
  ['1093']={'ゲメル','Gemel'}, ['1094']={'ズック','Zuck'}, ['1095']={'バルベーア','Barbea'}, ['1096']={'クリースヒルデ','Kreishilde'}, ['1097']={'アルギン','Argin'}, ['1098']={'アグレオ','Agreo'}, ['1099']={'育ての老人','Guardian Elder'}, ['11']={'エイヴリー','Avery'},
  ['110']={'ショーン','Shawn'}, ['1100']={'ロクセラーナ','Roxelana'}, ['1101']={'バベル','Babel'}, ['1102']={'リベカ','Rebeka'}, ['1103']={'ユスト','Yust'}, ['1104']={'ファラーナク','Faranak'}, ['1105']={'ウォーデン','Warden'}, ['1106']={'グリナス','Grinus'},
  ['1107']={'エレイン','Elaine'}, ['1108']={'オーベルン','Obern'}, ['1109']={'鎧男','The Armored Man'}, ['111']={'セス','Seth'}, ['112']={'シェーン','Shane'}, ['113']={'スペンサー','Spencer'}, ['114']={'スティーブン','Steven'}, ['115']={'スティーブ','Steve'},
  ['116']={'スチュアート','Stuart'}, ['117']={'タナー','Tanner'}, ['118']={'トーマス','Thomas'}, ['119']={'ティモシー','Timothy'}, ['12']={'ベイジル','Basil'}, ['120']={'トビー','Toby'}, ['121']={'トッド','Todd'}, ['122']={'トラヴィス','Travis'},
  ['123']={'トレバー','Trevor'}, ['124']={'トロイ','Troy'}, ['125']={'タイラー','Tyler'}, ['126']={'ヴィクター','Victor'}, ['127']={'ヴィンセント','Vincent'}, ['128']={'ウェイド','Wade'}, ['129']={'ウォルター','Walter'}, ['13']={'バーナード','Bernard'},
  ['130']={'ウォーレン','Warren'}, ['131']={'ウェスリー','Wesley'}, ['132']={'ワイアット','Wyatt'}, ['133']={'ザカリー','Zachary'}, ['134']={'ゼイン','Zane'}, ['135']={'アビゲイル','Abigail'}, ['136']={'アディソン','Addison'}, ['137']={'アイシャ','Aisha'},
  ['138']={'アレクシス','Alexis'}, ['139']={'アリス','Alice'}, ['14']={'ブレイク','Blake'}, ['140']={'アリシア','Alicia'}, ['141']={'アリソン','Allison'}, ['142']={'アリッサ','Alyssa'}, ['143']={'アマンダ','Amanda'}, ['144']={'アンバー','Amber'},
  ['145']={'アメリア','Amelia'}, ['146']={'エイミー','Amy'}, ['147']={'アンドレア','Andrea'}, ['148']={'アンナ','Anna'}, ['149']={'アナベル','Annabel'}, ['15']={'ブラッドリー','Bradley'}, ['150']={'アン','Ann'}, ['151']={'アリエル','Ariel'},
  ['152']={'アシュリー','Ashley'}, ['153']={'オードリー','Audrey'}, ['154']={'アヴァ','Ava'}, ['155']={'エイヴリー','Avery'}, ['156']={'ベイリー','Bailey'}, ['157']={'ベラ','Bella'}, ['158']={'ベサニー','Bethany'}, ['159']={'ビアンカ','Bianca'},
  ['16']={'ブランドン','Brandon'}, ['160']={'ブリアナ','Briana'}, ['161']={'ブリジット','Bridget'}, ['162']={'ブリン','Brynn'}, ['163']={'ケイトリン','Caitlin'}, ['164']={'カミラ','Camila'}, ['165']={'カミーユ','Camille'}, ['166']={'カーラ','Carla'},
  ['167']={'カーリー','Carly'}, ['168']={'カルメン','Carmen'}, ['169']={'キャロライン','Caroline'}, ['17']={'ブライアン','Brian'}, ['170']={'カサンドラ','Cassandra'}, ['171']={'キャシディ','Cassidy'}, ['172']={'キャサリン','Catherine'}, ['173']={'セシリア','Cecilia'},
  ['174']={'シャーロット','Charlotte'}, ['175']={'チェルシー','Chelsea'}, ['176']={'クロエ','Chloe'}, ['177']={'クリスティーナ','Christeina'}, ['178']={'クレア','Claire'}, ['179']={'クララ','Clara'}, ['18']={'ケイレブ','Caleb'}, ['180']={'クローディア','Claudia'},
  ['181']={'コートニー','Courtney'}, ['182']={'クリスタル','Crystal'}, ['183']={'デイジー','Daisy'}, ['184']={'ダコタ','Dakota'}, ['185']={'ダフネ','Daphne'}, ['186']={'ディライラ','Delilah'}, ['187']={'デスティニー','Destiny'}, ['188']={'ダイアナ','Diana'},
  ['189']={'エデン','Eden'}, ['19']={'カルヴィン','Calvin'}, ['190']={'エレイナ','Elaina'}, ['191']={'エレナ','Elena'}, ['192']={'エリアナ','Eliana'}, ['193']={'エリーズ','Elise'}, ['194']={'エリザ','Eliza'}, ['195']={'エリザベス','Elizabeth'},
  ['196']={'エラ','Ella'}, ['197']={'エリー','Ellie'}, ['198']={'エロイーズ','Eloise'}, ['199']={'エミリー','Emily'}, ['2']={'アーロン','Aaron'}, ['20']={'キャメロン','Cameron'}, ['200']={'エマ','Emma'}, ['201']={'エリカ','Erica'},
  ['202']={'エリン','Erin'}, ['203']={'エスメ','Esme'}, ['204']={'エヴァ','Eva'}, ['205']={'エヴリン','Evelyn'}, ['206']={'フェイス','Faith'}, ['207']={'フィオナ','Fiona'}, ['208']={'ガブリエラ','Gabriella'}, ['209']={'ジェマ','Gemma'},
  ['21']={'クラレンス','Clarence'}, ['210']={'ジェネシス','Genesis'}, ['211']={'ジアナ','Gianna'}, ['212']={'グレース','Grace'}, ['213']={'ヘイリー','Hailey'}, ['214']={'ヘイリー','Hailey'}, ['215']={'ハンナ','Hannah'}, ['216']={'ハーパー','Harper'},
  ['217']={'ヘイゼル','Hazel'}, ['218']={'ヘザー','Heather'}, ['219']={'ホリー','Holly'}, ['22']={'コーディ','Cody'}, ['220']={'ホープ','Hope'}, ['221']={'イザベラ','Isabella'}, ['222']={'イザベル','Isabel'}, ['223']={'イスラ','Isla'},
  ['224']={'アイビー','Ivy'}, ['225']={'ジェイド','Jade'}, ['226']={'ジェイミー','Jamie'}, ['227']={'ジャスミン','Jasmine'}, ['228']={'ジェナ','Jenna'}, ['229']={'ジェニファー','Jennifer'}, ['23']={'コリン','Colin'}, ['230']={'ジェシカ','Jessica'},
  ['231']={'ジリアン','Jillian'}, ['232']={'ジョアンナ','Joanna'}, ['233']={'ジョーディ','Jordy'}, ['234']={'ジュリア','Julia'}, ['235']={'カイラ','Kayla'}, ['236']={'カイリー','Kylie'}, ['237']={'カレン','Karen'}, ['238']={'カイラ','Kayla'},
  ['239']={'ケイラ','Keira'}, ['24']={'コナー','Connor'}, ['240']={'ケリー','Kelly'}, ['241']={'ケンドラ','Kendra'}, ['242']={'ケンジー','Kenzie'}, ['243']={'キンバリー','Kimberly'}, ['244']={'キンジー','Kinsey'}, ['245']={'クリスティン','Kristine'},
  ['246']={'クリスタル','Crystal'}, ['247']={'クロエ','Chloe'}, ['248']={'ライラ','Lyla'}, ['249']={'レイチェル','Rachel'}, ['25']={'サイラス','Silas'}, ['250']={'レイラ','Layla'}, ['251']={'レイナ','Reyna'}, ['252']={'レネー','Renée'},
  ['253']={'リア','Leah'}, ['254']={'リアナ','Liana'}, ['255']={'リリアン','Lillian'}, ['256']={'リサ','Lisa'}, ['257']={'リズ','Liz'}, ['258']={'ローラ','Laura'}, ['259']={'ローレン','Lauren'}, ['26']={'デイル','Dale'},
  ['260']={'レイシー','Lacey'}, ['261']={'レイラ','Layla'}, ['262']={'レクシー','Lexie'}, ['263']={'リリー','Lily'}, ['264']={'リンダ','Linda'}, ['265']={'リンゼイ','Lindsay'}, ['266']={'リンジー','Lindsey'}, ['267']={'アーミン','Armin'},
  ['268']={'アウグスト','August'}, ['269']={'アクサード','Aksad'}, ['27']={'ダニエル','Daniel'}, ['270']={'バルテン','Balten'}, ['271']={'アルバン','Alban'}, ['272']={'アルフリッド','Alfrid'}, ['273']={'アルベール','Albert'}, ['274']={'アンドラス','Andras'},
  ['275']={'イグナル','Ignal'}, ['276']={'イゾルト','Isolde'}, ['277']={'ヴァーレンフリート','Valenfried'}, ['278']={'ヴァルテル','Walter'}, ['279']={'ヴィーラント','Wieland'}, ['28']={'ダリウス','Darius'}, ['280']={'ヴィクトル','Viktor'}, ['281']={'ヴィルシャード','Vilschard'},
  ['282']={'ヴィンツェン','Vinzenz'}, ['283']={'ウーヴェ','Uwe'}, ['284']={'ヴェルナー','Werner'}, ['285']={'ウォルフディーン','Wolfdin'}, ['286']={'ヴォルフラム','Wolfram'}, ['287']={'ウルリッヒ','Ulrich'}, ['288']={'エードゥアルト','Eduard'}, ['289']={'エドガム','Edgam'},
  ['29']={'ダレン','Daren'}, ['290']={'エミールザル','Emilsar'}, ['291']={'エリッヒ','Erich'}, ['292']={'エルヴィン','Elvin'}, ['293']={'ゴーダル','Godal'}, ['294']={'ベルント','Bernd'}, ['295']={'オットマール','Ottmar'}, ['296']={'カーメン','Carmen'},
  ['297']={'カイザル','Kaiser'}, ['298']={'カルス','Kars'}, ['299']={'カルステンバルド','Carstenbald'}, ['3']={'エイデン','Aiden'}, ['30']={'ディーン','Dean'}, ['300']={'ギュンター','Gunther'}, ['301']={'クヴィリン','Quirin'}, ['302']={'クサンダー','Xander'},
  ['303']={'クラルス','Klaus'}, ['304']={'クリストフ','Christoph'}, ['305']={'クリストマード','Christomard'}, ['306']={'クルツァム','Kurtzam'}, ['307']={'グレゴール','Gregor'}, ['308']={'ゲッツォ','Goetz'}, ['309']={'ゲルヴィン','Gelwin'}, ['31']={'デクラン','Declan'},
  ['310']={'ゲルノート','Gernot'}, ['311']={'ゲルハム','Gerham'}, ['312']={'ゴートラム','Gotthram'}, ['313']={'コルバン','Corban'}, ['314']={'コンラッド','Conrad'}, ['315']={'ザミュエル','Samyuel'}, ['316']={'ジークファル','Siegfald'}, ['317']={'ジークフリート','Siegfried'},
  ['318']={'ジグムント','Sigmund'}, ['319']={'シモン','Simon'}, ['32']={'デニス','Denis'}, ['320']={'シュタールマン','Stahlmann'}, ['321']={'シュテルム','Sturm'}, ['322']={'ジルヴァス','Sylvas'}, ['323']={'ジルヴェスター','Sylvester'}, ['324']={'ゼーノン','Zenon'},
  ['325']={'ゼバス','Zebas'}, ['326']={'ダヴィド','David'}, ['327']={'ダリウスヴァルト','Dariusvald'}, ['328']={'ディーダーン','Deedan'}, ['329']={'ティルシャド','Tilshad'}, ['33']={'デレク','Derek'}, ['330']={'テオマル','Theomar'}, ['331']={'デトレフ','Detlef'},
  ['332']={'トーヴィアス','Tobias'}, ['333']={'バヘルンド','Baherund'}, ['334']={'トルステンパルド','Thorstenpald'}, ['335']={'ニクラスダード','Niklasdard'}, ['336']={'ヴァンダー','Vandar'}, ['337']={'ノルベルト','Norbert'}, ['338']={'ハーゲン','Hagen'}, ['339']={'ベルトラム','Bertram'},
  ['34']={'ドミニク','Dominic'}, ['340']={'ハイマール','Heimar'}, ['341']={'パウル','Paul'}, ['342']={'ハラル','Harald'}, ['343']={'バルダール','Baldur'}, ['344']={'ハルトムート','Hartmut'}, ['345']={'ハンス','Hans'}, ['346']={'ファーダン','Fardan'},
  ['347']={'ファルク','Falk'}, ['348']={'フィリップシャー','Philippscher'}, ['349']={'トゥスカーン','Tuscan'}, ['35']={'ダグラス','Douglas'}, ['350']={'フェルクス','Felix'}, ['351']={'フェルディンド','Ferdinand'}, ['352']={'フランダル','Flandal'}, ['353']={'フリーデル','Friedel'},
  ['354']={'フリッツ','Fritz'}, ['355']={'ブルーノ','Bruno'}, ['356']={'ペテル','Peter'}, ['357']={'ヘニング','Henning'}, ['358']={'ベネディクト','Benedikt'}, ['359']={'ヘルヴァーン','Helvern'}, ['36']={'ディラン','Dylan'}, ['360']={'ヘルフリード','Helfried'},
  ['361']={'ヘルムバラン','Helmbaln'}, ['362']={'ベルン','Bern'}, ['363']={'ベルント','Bernd'}, ['364']={'ヘンドリク','Hendrik'}, ['365']={'ボードマル','Bodmar'}, ['366']={'ボートル','Bortl'}, ['367']={'ホルガーン','Holgarn'}, ['368']={'マーカム','Markham'},
  ['369']={'マティアシュ','Matthias'}, ['37']={'エドガー','Edgar'}, ['370']={'マルヴァルド','Malvald'}, ['371']={'マルタン','Martin'}, ['372']={'マルツェル','Marzel'}, ['373']={'マンフレッド','Manfred'}, ['374']={'ミヒャザム','Michasam'}, ['375']={'ヤーコブ','Jakob'},
  ['376']={'ユスト','Justus'}, ['377']={'ユルゲン','Jurgen'}, ['378']={'キルサーム','Kilsam'}, ['379']={'ヨーナスザード','Jonaszade'}, ['38']={'エドウィン','Edwin'}, ['380']={'ヨゼル','Josef'}, ['381']={'ヨハン','Johann'}, ['382']={'ライナーフ','Rainerf'},
  ['383']={'ライノルフ','Reinolf'}, ['384']={'ライマー','Reimer'}, ['385']={'ライムンドシャウリ','Raimundshauri'}, ['386']={'ラバーン','Rabarn'}, ['387']={'ラルフ','Ralph'}, ['388']={'ランベルト','Lambert'}, ['389']={'リューディン','Rudin'}, ['39']={'イーライアス','Elias'},
  ['390']={'ルードヴィン','Ludwin'}, ['391']={'ルトガーシュ','Rutgersch'}, ['392']={'ルカルス','Lukars'}, ['393']={'ルドルフシャー','Rudolfshar'}, ['394']={'レイムン','Reimund'}, ['395']={'レオポルト','Leopold'}, ['396']={'レオンハルト','Leonhart'}, ['397']={'レナート','Renat'},
  ['398']={'ローラント','Roland'}, ['399']={'ロタール','Lothar'}, ['4']={'アラン','Aran'}, ['40']={'エリオット','Elliot'}, ['400']={'アイカシャ','Aikasha'}, ['401']={'アストリット','Astrid'}, ['402']={'アニタ','Anita'}, ['403']={'アルマダーン','Armadaan'},
  ['404']={'アレクサドラ','Alexandra'}, ['405']={'アンネリース','Anneliese'}, ['406']={'イゾルデ','Isolde'}, ['407']={'イルマ','Irma'}, ['408']={'イングリット','Ingrid'}, ['409']={'インゲボルグ','Ingeborg'}, ['41']={'イーサン','Ethan'}, ['410']={'ヴァルトラ','Waltra'},
  ['411']={'ヴァルプルガ','Walpurga'}, ['412']={'ヴィクトリアード','Victoriad'}, ['413']={'ヴィブケ','Vibeke'}, ['414']={'ヴィルマ','Wilma'}, ['415']={'ウータ','Uta'}, ['416']={'ヴェロニカパール','Veronicaperl'}, ['417']={'ウルス','Urs'}, ['418']={'ウルリケ','Ulrike'},
  ['419']={'エディタ','Edita'}, ['42']={'エヴェレット','Everett'}, ['420']={'エファナ','Ephana'}, ['421']={'エリザファー','Elizaphar'}, ['422']={'エリシュカ','Eliska'}, ['423']={'エルナ','Erna'}, ['424']={'エルフリデ','Elfride'}, ['425']={'エルミラ','Elmir'},
  ['426']={'エルメリンデ','Elmerinde'}, ['427']={'オーロル','Aurore'}, ['428']={'オルトルーン','Ortrun'}, ['429']={'カトリーナ','Katrina'}, ['43']={'イズラ','Izra'}, ['430']={'カリーナ','Carina'}, ['431']={'ギゼラ','Gisela'}, ['432']={'クーアグンデ','Cuegonde'},
  ['433']={'グードルン','Gudrun'}, ['434']={'クラーラ','Clara'}, ['435']={'クラウディア','Claudia'}, ['436']={'クリスティナ','Christina'}, ['437']={'クレメンタ','Clementa'}, ['438']={'クレメンティーネ','Clementine'}, ['439']={'クローティルデ','Clothilde'}, ['44']={'フランク','Frank'},
  ['440']={'ゲルダ','Gerda'}, ['441']={'ゴゼル','Gosel'}, ['442']={'コルネラ','Cornela'}, ['443']={'コンスタンツァ','Constanza'}, ['444']={'ザスキア','Saskia'}, ['445']={'サビナ','Sabina'}, ['446']={'ジーグルーン','Sigrun'}, ['447']={'ジーグルーン','Sigrun'},
  ['448']={'ジグリット','Sigrid'}, ['449']={'ジビル','Sibyl'}, ['45']={'ギャレット','Garrett'}, ['450']={'ジモーネ','Simone'}, ['451']={'シュヴァーナ','Schwana'}, ['452']={'シュテフィ','Steffi'}, ['453']={'ジルケ','Silke'}, ['454']={'ゼーラ','Selah'},
  ['455']={'ゼーラバーズ','Selabars'}, ['456']={'ゼナー','Sener'}, ['457']={'セリーン','Selene'}, ['458']={'ゾフィーラ','Zophira'}, ['459']={'ゾライダ','Zoraida'}, ['46']={'ギャビン','Gavin'}, ['460']={'ダグマール','Dagmar'}, ['461']={'タティヤナ','Tatiana'},
  ['462']={'ツェツィーリア','Cacilia'}, ['463']={'ディアナ','Diana'}, ['464']={'ティルカーシャ','Tilkarsha'}, ['465']={'テレジアマーフ','Theresa-Marf'}, ['466']={'ドルテ','Dorte'}, ['467']={'ナターシャ','Natasha'}, ['468']={'ニコラ','Nicola'}, ['469']={'ノエミ','Noemi'},
  ['47']={'グレアム','Graham'}, ['470']={'ノラシャー','Noresha'}, ['471']={'パウリナ','Paulina'}, ['472']={'ハドルーン','Hadlrun'}, ['473']={'ハネル','Hannel'}, ['474']={'バルバラード','Barbarad'}, ['475']={'ビアンカパルド','Biancapard'}, ['476']={'ビルギッタ','Birgitta'},
  ['477']={'ヒルデマーフ','Hildemar'}, ['478']={'ファレン','Valen'}, ['479']={'ファレンターネ','Valentin'}, ['48']={'グレゴリー','Gregory'}, ['480']={'フィベル','Fibel'}, ['481']={'フェオドラ','Feodora'}, ['482']={'フェリネ','Feline'}, ['483']={'フランツィスカ','Franziska'},
  ['484']={'フリーバ','Friba'}, ['485']={'ブリギット','Brigitte'}, ['486']={'ブリュンヒルデ','Brunhilde'}, ['487']={'フレダ','Frieda'}, ['488']={'ベアトリス','Beatrice'}, ['489']={'ヘダガル','Hedagar'}, ['49']={'ハリソン','Harrison'}, ['490']={'ベティーナ','Bettina'},
  ['491']={'ペトラバヌ','Petra-Banu'}, ['492']={'ヘルガルン','Helgarn'}, ['493']={'ヘルミネ','Hermine'}, ['494']={'ヘレナー','Helena'}, ['495']={'ベレニス','Berenice'}, ['496']={'マーレンシャヤ','Marenshaya'}, ['497']={'マリアン','Marianne'}, ['498']={'マルヴィーナ','Malvina'},
  ['499']={'マルガナ','Margana'}, ['5']={'アルバート','Albert'}, ['50']={'ハーヴェイ','Harvey'}, ['500']={'マルタ','Marta'}, ['501']={'マルタラーシェ','Martalarche'}, ['502']={'マンダ','Manda'}, ['503']={'メヒト','Mecht'}, ['504']={'メルナ','Merna'},
  ['505']={'モニカファル','Monika-Fal'}, ['506']={'ヤスミンヴァル','Jasminval'}, ['507']={'ヤネット','Janett'}, ['508']={'ユタザル','Jutazar'}, ['509']={'ユリアネ','Juliane'}, ['51']={'ヘンリー','Henry'}, ['510']={'ヨーナヤス','Yonayasu'}, ['511']={'ヨゼフィン','Josefine'},
  ['512']={'ラウラ','Laura'}, ['513']={'ラデグンデ','Radegund'}, ['514']={'ラモナ','Ramona'}, ['515']={'リーザ','Lisa'}, ['516']={'リカルダ','Ricarda'}, ['517']={'リズベット','Lisbeth'}, ['518']={'リリアン','Lillian'}, ['519']={'ルイゼ','Luise'},
  ['52']={'ハワード','Howard'}, ['520']={'ルートガルディス','Rutgardis'}, ['521']={'ルティア','Lutia'}, ['522']={'レアーナ','Leana'}, ['523']={'レアナーズ','Leonardis'}, ['524']={'レオフリデ','Leofride'}, ['525']={'レギーネ','Regine'}, ['526']={'レナテム','Renatem'},
  ['527']={'ローヴィサ','Lovisa'}, ['528']={'ローザリン','Rosalin'}, ['529']={'ローラパール','Lauraperl'}, ['53']={'ヒュー','Hugh'}, ['530']={'ロズヴィタ','Rosvita'}, ['531']={'ロタール','Lothar'}, ['532']={'アリフ','Alif'}, ['533']={'アルダシール','Ardashir'},
  ['534']={'アルプラン','Alplan'}, ['535']={'イスケンデル','Iskandar'}, ['536']={'イスフェンド','Isfand'}, ['537']={'イナルベク','Inalbek'}, ['538']={'イルカーン','Ilkhan'}, ['539']={'イルテリシュ','Ilterish'}, ['54']={'イアン','Ian'}, ['540']={'イルハムダル','Ilhamdar'},
  ['541']={'イルファン','Ilfan'}, ['542']={'イルマズィード','Ilmazid'}, ['543']={'ヴァーヒド','Vahid'}, ['544']={'ウイグルン','Uygurun'}, ['545']={'ヴェダト','Vedat'}, ['546']={'ヴェリアン','Velian'}, ['547']={'ヴォルカンシャー','Volkanshah'}, ['548']={'ウグルシャド','Ughulshad'},
  ['549']={'ウズベキル','Uzbekir'}, ['55']={'アイザック','Isaac'}, ['550']={'ウムトハン','Umuthan'}, ['551']={'ウラシュ','Urash'}, ['552']={'ウンゲリル','Ungeril'}, ['553']={'エシュレフ','Eshref'}, ['554']={'エムレカン','Emrekan'}, ['555']={'エムレベク','Emrebek'},
  ['556']={'エルキン','Erkin'}, ['557']={'エルテキン','Ertekin'}, ['558']={'エルドアン','Erdogan'}, ['559']={'オクタイ','Oktay'}, ['56']={'アイザイア','Isaiah'}, ['560']={'オズグル','Ozgur'}, ['561']={'オスマノール','Osmanoglu'}, ['562']={'オルハン','Orhan'},
  ['563']={'オルベク','Orbek'}, ['564']={'カイラン','Kayran'}, ['565']={'カザンベク','Kazanbek'}, ['566']={'ガズネヴィ','Ghaznevi'}, ['567']={'カプランベク','Kaplanbek'}, ['568']={'カラルハン','Karalhan'}, ['569']={'カレンデール','Kalendael'}, ['57']={'アイヴァン','Ivan'},
  ['570']={'ギャーセディン','Gyaseddin'}, ['571']={'キルシャド','Kilshad'}, ['572']={'ギルテキン','Gultekin'}, ['573']={'クトルグ','Kutlug'}, ['574']={'ケナンベク','Kenanbek'}, ['575']={'ケリムザード','Kerimzade'}, ['576']={'ゴクトゥルク','Gokturk'}, ['577']={'ゴルカン','Gorkan'},
  ['578']={'コルクトゥール','Korkuturl'}, ['579']={'サーサン','Sasan'}, ['58']={'ジェイコブ','Jacob'}, ['580']={'サドリディン','Sadriddin'}, ['581']={'ザヒル','Zahir'}, ['582']={'サマンル','Samanlu'}, ['583']={'ザリル','Zaril'}, ['584']={'ザンドルク','Zandruk'},
  ['585']={'ジェミルカーン','Cemilkan'}, ['586']={'シェムシ','Semsi'}, ['587']={'ジェムスィン','Cemsin'}, ['588']={'ジェラーリ','Celali'}, ['589']={'ジェンギズシャー','Cengizshah'}, ['59']={'ジャレッド','Jared'}, ['590']={'シナンパシャ','Sinan Pasha'}, ['591']={'ジハンギル','Cihangir'},
  ['592']={'ジヤーウル','Ziaul'}, ['593']={'ジャーフェル','Jafar'}, ['594']={'ジャヴィード','Javid'}, ['595']={'シャプール','Shapur'}, ['596']={'シャムシディン','Shamseddin'}, ['597']={'ズベイラード','Zubeyrad'}, ['598']={'スルタンベキル','Sultanbekir'}, ['599']={'セイフディン','Seyfeddin'},
  ['6']={'アロンゾ','Alonzo'}, ['60']={'ジェイソン','Jason'}, ['600']={'ゼケルヤーン','Zekeriya'}, ['601']={'セラーミ','Selami'}, ['602']={'セルカン','Serkan'}, ['603']={'ソーラブ','Sohrab'}, ['604']={'ダーラ','Dara'}, ['605']={'タジュディン','Tajuddin'},
  ['606']={'タフマス','Tahmasp'}, ['607']={'ダリアン','Dariyan'}, ['608']={'タルカーン','Tarkan'}, ['609']={'タルハン','Tarhan'}, ['61']={'ジャスパー','Jasper'}, ['610']={'チャガタイ','Chagatai'}, ['611']={'テキンアルプ','Tekinalp'}, ['612']={'デミルシャン','Demirshan'},
  ['613']={'テムチン','Temujin'}, ['614']={'デリベク','Delibek'}, ['615']={'トゥメンベイ','Tumenbey'}, ['616']={'トグルルベク','Toghrulbek'}, ['617']={'ドルムシュベク','Durmusbek'}, ['618']={'ナーゼルベク','Nazerbek'}, ['619']={'ナディル','Nadir'}, ['62']={'ジェフリー','Geoffrey'},
  ['620']={'ヌレッディン','Nureddin'}, ['621']={'ネヴァイル','Neville'}, ['622']={'ネジム','Nedjim'}, ['623']={'ハーカン','Hakan'}, ['624']={'パーヤム','Payam'}, ['625']={'ハールーン','Harun'}, ['626']={'パシャベク','Pashabek'}, ['627']={'バトゥハン','Batu Han'},
  ['628']={'バハドゥル','Bahadir'}, ['629']={'バヤズィット','Bayezid'}, ['63']={'ジェレミー','Jeremy'}, ['630']={'ハリトシャ','Haritshah'}, ['631']={'ビラルシャ','Bilalshah'}, ['632']={'ビルゲカン','Bilge-Khan'}, ['633']={'ファラーマルズ','Faramarz'}, ['634']={'ファルーク','Faruq'},
  ['635']={'ファルザーン','Farzan'}, ['636']={'フーシャン','Houshang'}, ['637']={'フェリドゥン','Fereydun'}, ['638']={'フェルハドル','Farhadur'}, ['639']={'フマイーン','Homayun'}, ['64']={'ジェシー','Jesse'}, ['640']={'フルカンル','Furkanlu'}, ['641']={'ペイケル','Peyker'},
  ['642']={'ペルヴェズ','Pervez'}, ['643']={'ベルケカン','Berke-Khan'}, ['644']={'ホスロフ','Khosrow'}, ['645']={'ボルカーン','Burkan'}, ['646']={'マンスール','Mansur'}, ['647']={'ミルザン','Mirzan'}, ['648']={'ムザッフェル','Muzaffar'}, ['649']={'ムラトシャー','Muradshah'},
  ['65']={'ジョエル','Joel'}, ['650']={'メフルザード','Mehrzad'}, ['651']={'メルトゥン','Mertun'}, ['652']={'モフセン','Mohsen'}, ['653']={'ヤーシン','Yasin'}, ['654']={'ヤシャルベク','Yasar Bek'}, ['655']={'ヤマン','Yaman'}, ['656']={'ラフマーン','Rahman'},
  ['657']={'ラミザード','Ramizad'}, ['658']={'リュステムル','Rustemur'}, ['659']={'リュトフィー','Lutfi'}, ['66']={'ジョン','John'}, ['660']={'ルステムシャー','Rustemshah'}, ['661']={'レヴェントルク','Levent Turk'}, ['662']={'レザエル','Rezael'}, ['663']={'レシャド','Resad'},
  ['664']={'ロスタムカーン','Rostam-Khan'}, ['665']={'アイグル','Aygul'}, ['666']={'アイテムール','Aitemur'}, ['667']={'アイパレル','Ayparel'}, ['668']={'アイラ','Ayla'}, ['669']={'アスマーハン','Asmahan'}, ['67']={'ジョセフ','Joseph'}, ['670']={'アフタバーヌ','Aftabanu'},
  ['671']={'アライーシュ','Arayish'}, ['672']={'アルズハン','Arzhan'}, ['673']={'イクラール','Ikrar'}, ['674']={'イシュラー','Ishra'}, ['675']={'イスミハン','Ismihan'}, ['676']={'イペッキ','Ipekki'}, ['677']={'イラーヘ','Ilahe'}, ['678']={'イルマクル','Ilmakur'},
  ['679']={'イルミナーラ','Ilminara'}, ['68']={'ジョシュア','Joshua'}, ['680']={'ヴァルデ','Valde'}, ['681']={'ヴィルダー','Wilder'}, ['682']={'ヴェハーレ','Vehare'}, ['683']={'ヴェファルーン','Vefarun'}, ['684']={'ウフテーデ','Ufteide'}, ['685']={'ウルカーネ','Ulcane'},
  ['686']={'ウンマール','Ummar'}, ['687']={'ウンムルシャ','Ummurshah'}, ['688']={'エスラーグル','Esragul'}, ['689']={'エフレング','Efrenk'}, ['69']={'ジュリアン','Julian'}, ['690']={'エメルダー','Emeralda'}, ['691']={'エルヴァン','Elvan'}, ['692']={'エルギンヌール','Erginnur'},
  ['693']={'オイギュル','Oygur'}, ['694']={'オキアル','Okyar'}, ['695']={'オズレムスル','Ozlemsur'}, ['696']={'オルカイ','Orkai'}, ['697']={'オルタージャ','Ortaja'}, ['698']={'カフタングュル','Kaftangul'}, ['699']={'カメラーン','Kamran'}, ['7']={'アンドリュー','Andrew'},
  ['70']={'ジャスティン','Justin'}, ['700']={'ギュルシャーン','Gulsan'}, ['701']={'ギュルシャーン','Gulsan'}, ['702']={'ギュルナズ','Gulnaz'}, ['703']={'グルシマン','Gulsiman'}, ['704']={'グルバーヌ','Gulbanu'}, ['705']={'ケルベール','Kerbel'}, ['706']={'ケレスターン','Kelestan'},
  ['707']={'ゴルナーズ','Golnaz'}, ['708']={'サーレハン','Salehan'}, ['709']={'ザリーンタージ','Zarrintaj'}, ['71']={'キース','Keith'}, ['710']={'シーリーン','Sirin'}, ['711']={'ジェヴリェー','Cevriye'}, ['712']={'シェフナーズ','Sefnaz'}, ['713']={'シターレグル','Sitaregul'},
  ['714']={'シャーディーヌ','Shadinu'}, ['715']={'ジャヴァヘール','Javaher'}, ['716']={'シャカイェグ','Shakayeg'}, ['717']={'ジャナーラ','Janara'}, ['718']={'シャファクシェン','Shafakshen'}, ['719']={'シャフザディ','Shahzadi'}, ['72']={'ケネス','Kenneth'}, ['720']={'シャフルザーデ','Shahrzad'},
  ['721']={'ジャミーラ','Jamila'}, ['722']={'シャムシャナー','Shamshanar'}, ['723']={'シュクラニェ','Shukraniye'}, ['724']={'ジラーンフェル','Jiranfer'}, ['725']={'スナイハーヌム','Sunay Hanum'}, ['726']={'ゼベルジェド','Zeberjed'}, ['727']={'セラーエル','Sera\'el'}, ['728']={'セルマーシャン','Selmarshan'},
  ['729']={'ソナイグル','Sonaygul'}, ['73']={'ケビン','Kevin'}, ['730']={'ターラーン','Taran'}, ['731']={'チェシュミーナ','Cheshmina'}, ['732']={'チェムシナー','Chemsinar'}, ['733']={'チャーラーフェル','Charafel'}, ['734']={'ディララ','Dilara'}, ['735']={'ディルシェン','Dilshen'},
  ['736']={'ティルバー','Tilbar'}, ['737']={'ディレクシャ','Direksha'}, ['738']={'デフネル','Defnel'}, ['739']={'デュリュシャー','Duryusha'}, ['74']={'カイル','Kyle'}, ['740']={'テュルキャン','Turkan'}, ['741']={'デュルダネー','Duldane'}, ['742']={'テルメサル','Termesar'},
  ['743']={'トゥムルー','Tumru'}, ['744']={'トゥライ','Turay'}, ['745']={'ナーヴァゼル','Nawazel'}, ['746']={'ナールゲス','Nargis'}, ['747']={'ニハーナー','Nihana'}, ['748']={'ネイイレン','Neyiren'}, ['749']={'ネシェルバン','Neshirvan'}, ['75']={'ランス','Lance'},
  ['750']={'ネジュマーラ','Nejmara'}, ['751']={'ネスリーン','Nesrin'}, ['752']={'ネフィーセハ','Nefiseha'}, ['753']={'ネフェルー','Neferu'}, ['754']={'パールヴァーン','Parvan'}, ['755']={'ハザールナズ','Hazarnaz'}, ['756']={'ハザーレン','Hazaren'}, ['757']={'ハニフェ','Hanife'},
  ['758']={'パリザード','Parizad'}, ['759']={'ビナーズ','Binaz'}, ['76']={'ローレンス','Lawrence'}, ['760']={'ビルギンス','Bilgins'}, ['761']={'ファーティマ','Fatima'}, ['762']={'フィダンギュル','Fidangul'}, ['763']={'フィローゼグル','Firozegul'}, ['764']={'フールザーデ','Furzade'},
  ['765']={'フェリシュテ','Fereshte'}, ['766']={'フェルーゼア','Firuzeah'}, ['767']={'フェルザーネ','Farzaneh'}, ['768']={'フェルヤーナ','Faryana'}, ['769']={'フマーヤ','Humaya'}, ['77']={'レオ','Leo'}, ['770']={'フルガーネ','Fulgane'}, ['771']={'ペイマーン','Peyman'},
  ['772']={'ベヘシュテール','Beheshtar'}, ['773']={'ペリハン','Perihan'}, ['774']={'ベリル','Beryl'}, ['775']={'ペリンシャー','Perinsha'}, ['776']={'ベルギンサル','Belginsar'}, ['777']={'マーニラーン','Maniran'}, ['778']={'マーフヴァシュ','Mahvash'}, ['779']={'マーヘリヤ','Mahriya'},
  ['78']={'レオン','Leon'}, ['780']={'ミネヴェル','Minevel'}, ['781']={'メフラーン','Mehran'}, ['782']={'メフルバヌ','Mehrbanu'}, ['783']={'メリシェル','Merisher'}, ['784']={'ヤイルン','Yairun'}, ['785']={'ヤセミンザード','Yaseminzad'}, ['786']={'ヤプラクルン','Yaprakrun'},
  ['787']={'ラーヴィヤ','Rawiya'}, ['788']={'ライラーフェル','Laylafel'}, ['789']={'ランヌシュ','Rannush'}, ['79']={'レヴィ','Levi'}, ['790']={'ルキヤーデ','Ruqiyada'}, ['791']={'ルバーバシャン','Rubabshan'}, ['792']={'ルベイダハン','Rubeidahan'}, ['793']={'レイラナーズ','Laylanaz'},
  ['794']={'レヤーンフェル','Rayanfer'}, ['795']={'ロクシャーナ','Rukhsana'}, ['796']={'アンネリース','Anneliese'}, ['797']={'レンヤ','Renya'}, ['798']={'カイリ','Kairi'}, ['799']={'ユウシン','Yushin'}, ['8']={'アンソニー','Anthony'}, ['80']={'リアム','Liam'},
  ['800']={'ソウマ','Soma'}, ['801']={'リント','Rinto'}, ['802']={'ジン','Jin'}, ['803']={'シュウヤ','Shuya'}, ['804']={'レイガ','Reiga'}, ['805']={'アラン','Aran'}, ['806']={'カイセイ','Kaisei'}, ['807']={'イツキ','Itsuki'},
  ['808']={'テムガ','Temga'}, ['809']={'リョウウン','Ryoun'}, ['81']={'ローガン','Logan'}, ['810']={'ケンリ','Kenri'}, ['811']={'サイガ','Saiga'}, ['812']={'バクト','Bakt'}, ['813']={'ゴウラン','Goran'}, ['814']={'フウガ','Fuga'},
  ['815']={'カグラ','Kagura'}, ['816']={'シノン','Shinon'}, ['817']={'トウリ','Tori'}, ['818']={'ナルキ','Naruki'}, ['819']={'ヒリュウ','Hiryu'}, ['82']={'ルーカス','Lucas'}, ['820']={'マヒル','Mahiru'}, ['821']={'ヤクモ','Yakumo'},
  ['822']={'ルイト','Ruito'}, ['823']={'ワタル','Wataru'}, ['824']={'イクヤ','Ikuya'}, ['825']={'エイシン','Eishin'}, ['826']={'オルグ','Orlg'}, ['827']={'カズマ','Kazuma'}, ['828']={'キシン','Kishin'}, ['829']={'クオン','Kuon'},
  ['83']={'ルーク','Luke'}, ['830']={'ゲント','Gento'}, ['831']={'コウヤ','Koya'}, ['832']={'ザイン','Zain'}, ['833']={'スイレン','Suiren'}, ['834']={'セレン','Seren'}, ['835']={'タキト','Takito'}, ['836']={'チハヤ','Chihaya'},
  ['837']={'ニキ','Niki'}, ['838']={'ネイラン','Neiran'}, ['839']={'ノアト','Noato'}, ['84']={'マーカス','Marcus'}, ['840']={'ハガン','Hagan'}, ['841']={'ビャク','Byaku'}, ['842']={'フェイロン','Feilong'}, ['843']={'ホクト','Hokuto'},
  ['844']={'マシロ','Mashiro'}, ['845']={'ミツキ','Mitsuki'}, ['846']={'ムツキ','Mutsuki'}, ['847']={'メイキ','Meiki'}, ['848']={'ヨウラン','Yoran'}, ['849']={'ランガ','Ranga'}, ['85']={'マーティン','Martin'}, ['850']={'リアン','Lian'},
  ['851']={'イスル','Isur'}, ['852']={'ヴェルガ','Velga'}, ['853']={'カイロン','Cairon'}, ['854']={'シグレ','Shigure'}, ['855']={'トライ','Trai'}, ['856']={'ベルケ','Berke'}, ['857']={'ライドウ','Raido'}, ['858']={'リシャル','Rishal'},
  ['859']={'ルファン','Lufan'}, ['86']={'メイソン','Mason'}, ['860']={'レンシン','Renshin'}, ['861']={'アスル','Asur'}, ['862']={'エリオン','Erion'}, ['863']={'キリュウ','Kiryu'}, ['864']={'シャリオ','Shario'}, ['865']={'テルキ','Teruki'},
  ['866']={'ドゥーレン','Duren'}, ['867']={'ナラン','Naran'}, ['868']={'パシク','Pasik'}, ['869']={'フィレン','Firen'}, ['87']={'マシュー','Matthew'}, ['870']={'マクベ','Makube'}, ['871']={'ヨイチ','Yoichi'}, ['872']={'イルデン','Ilden'},
  ['873']={'カイラ','Kayla'}, ['874']={'クルト','Kurt'}, ['875']={'ジェノン','Genon'}, ['876']={'スバル','Subaru'}, ['877']={'チグサ','Chigusa'}, ['878']={'ツルギ','Tsurugi'}, ['879']={'ハイレン','Hailen'}, ['88']={'マックスウェル','Maxwell'},
  ['880']={'メルグン','Mergun'}, ['881']={'ラカン','Rakan'}, ['882']={'ウリュウ','Uryu'}, ['883']={'オルド','Ordo'}, ['884']={'カサル','Kasar'}, ['885']={'キレン','Kilen'}, ['886']={'ソヨグ','Soyogu'}, ['887']={'タルン','Tarun'},
  ['888']={'ヘリオン','Helion'}, ['889']={'ヤシマ','Yashima'}, ['89']={'マイカ','Maika'}, ['890']={'リオン','Lyon'}, ['891']={'エンマ','Enma'}, ['892']={'ギジン','Gijin'}, ['893']={'ザン','Zan'}, ['894']={'テグラ','Tegura'},
  ['895']={'ネルグイ','Nelgui'}, ['896']={'ハクオウ','Hakuo'}, ['897']={'ファロン','Fallon'}, ['898']={'マガイ','Magai'}, ['899']={'ユグル','Yugur'}, ['9']={'アーサー','Arthur'}, ['90']={'マイルズ','Miles'}, ['900']={'ライキ','Raiki'},
  ['901']={'レンゾ','Renzo'}, ['902']={'ウォルン','Waln'}, ['903']={'カヤト','Kayat'}, ['904']={'ゲントウ','Gentoh'}, ['905']={'シヴァル','Shival'}, ['906']={'ダレン','Daren'}, ['907']={'ヒスイ','Hisui'}, ['908']={'リュウメイ','Ryumei'},
  ['909']={'アイガ','Aiga'}, ['91']={'ネイサン','Nathan'}, ['910']={'クラマ','Kurama'}, ['911']={'バイガル','Baigal'}, ['912']={'ホズミ','Hozumi'}, ['913']={'モンド','Mondo'}, ['914']={'ユーレン','Yuren'}, ['915']={'カザン','Kazan'},
  ['916']={'シエン','Shien'}, ['917']={'バルガ','Balga'}, ['918']={'レイジ','Reiji'}, ['919']={'コハク','Kohaku'}, ['92']={'ニール','Neil'}, ['920']={'タイガ','Taiga'}, ['921']={'ノワキ','Nowaki'}, ['922']={'ハヤト','Hayato'},
  ['923']={'リシン','Rishin'}, ['924']={'ミナギ','Minagi'}, ['925']={'ガジン','Gajin'}, ['926']={'ギラン','Giran'}, ['927']={'ヤマト','Yamato'}, ['928']={'バルド','Baldo'}, ['929']={'キョウゴ','Kyogo'}, ['93']={'ノア','Noah'},
  ['930']={'アイナ','Aina'}, ['931']={'アキホ','Akiho'}, ['932']={'アサギリ','Asagiri'}, ['933']={'アサネ','Asane'}, ['934']={'アズハ','Azuha'}, ['935']={'アヤメ','Ayame'}, ['936']={'アヤリ','Ayari'}, ['937']={'イオリ','Iori'},
  ['938']={'イサナ','Isana'}, ['939']={'イズミ','Izumi'}, ['94']={'ノーラン','Nolan'}, ['940']={'イチル','Ichiru'}, ['941']={'イツナ','Itsuna'}, ['942']={'イルシエ','Ilchie'}, ['943']={'イロハ','Iroha'}, ['944']={'ウミカ','Umika'},
  ['945']={'ウルミナ','Urumina'}, ['946']={'ウルル','Ururu'}, ['947']={'エイリ','Eiri'}, ['948']={'エミネ','Emine'}, ['949']={'エリカ','Erica'}, ['95']={'オリバー','Oliver'}, ['950']={'エリハ','Eriha'}, ['951']={'エンファ','Enfa'},
  ['952']={'オトハ','Otoha'}, ['953']={'オミナ','Omina'}, ['954']={'オルシア','Orushia'}, ['955']={'カエデ','Kaede'}, ['956']={'カズハナ','Kazuhana'}, ['957']={'カスミ','Kasumi'}, ['958']={'カスリ','Kasuri'}, ['959']={'カナエ','Kanae'},
  ['96']={'オマル','Omaru'}, ['960']={'カヤノ','Kayano'}, ['961']={'カヤリ','Kayari'}, ['962']={'カンナ','Kanna'}, ['963']={'キョクメイ','Kyokumei'}, ['964']={'クニハ','Kuniha'}, ['965']={'クレナイ','Kurenai'}, ['966']={'ケイファ','Keifa'},
  ['967']={'ケイラ','Keira'}, ['968']={'コトネ','Kotone'}, ['969']={'コトハ','Kotoha'}, ['97']={'オーウェン','Owen'}, ['970']={'コヨミ','Koyomi'}, ['971']={'サクヤ','Sakuya'}, ['972']={'サヨリ','Sayori'}, ['973']={'サラサ','Sarasa'},
  ['974']={'サリナ','Sarina'}, ['975']={'シェンリ','Shenri'}, ['976']={'シグリ','Shiguri'}, ['977']={'シノメ','Shinome'}, ['978']={'シャナ','Shana'}, ['979']={'シャメイ','Shumei'}, ['98']={'パーカー','Parker'}, ['980']={'シュネリ','Shuneri'},
  ['981']={'シュレイ','Shurei'}, ['982']={'シュンカ','Shunka'}, ['983']={'スイハ','Suiha'}, ['984']={'スイリン','Suirin'}, ['985']={'スズカ','Suzuka'}, ['986']={'スズナ','Suzuna'}, ['987']={'スミカ','Sumika'}, ['988']={'スミレ','Sumire'},
  ['989']={'セツカ','Setsuka'}, ['99']={'プレストン','Preston'}, ['990']={'セツリ','Setsuri'}, ['991']={'セリアナ','Seriana'}, ['992']={'ソヨカ','Soyoka'}, ['993']={'ソラナ','Sorana'}, ['994']={'タミエ','Tamie'}, ['995']={'チェルマ','Cherma'},
  ['996']={'チサト','Chisato'}, ['997']={'チセリ','Chiseri'}, ['998']={'チャムナ','Chamuna'}, ['999']={'チャンユエ','Chanyue'},
}
GT.FAMILYNAMES = {  -- row name -&gt; {ja, en}  (DT_FamilyNameList)
  ['10']={'ウェッティン','Wettin'}, ['100']={'リンシン','Linxin'}, ['101']={'メイファ','Meifa'}, ['102']={'シュウラン','Xiulan'}, ['103']={'ハクレン','Bailian'}, ['104']={'フゥアン','Fu\'an'}, ['105']={'スイラン','Suilan'}, ['106']={'ナユタ','Nayuta'},
  ['107']={'ムルン','Murun'}, ['108']={'ヒメリ','Himeri'}, ['109']={'クノン','Kunon'}, ['11']={'エーバーハルト','Eberhardt'}, ['110']={'キサラ','Kisara'}, ['111']={'オリエ','Orie'}, ['112']={'タンファ','Tanhua'}, ['113']={'キョウレイ','Kyolei'},
  ['114']={'セリア','Celia'}, ['115']={'バイリン','Bailin'}, ['116']={'グァンファ','Guanghua'}, ['117']={'ヨリア','Yoria'}, ['118']={'ヤシロ','Yashiro'}, ['119']={'ユン','Yun'}, ['12']={'エッカルト','Eckhardt'}, ['120']={'ミナ','Mina'},
  ['121']={'ホア','Hoa'}, ['122']={'アスマ','Asuma'}, ['123']={'ナラン','Naran'}, ['124']={'スオウギ','Suogi'}, ['125']={'シュレン','Shuren'}, ['126']={'ラカン','Rakan'}, ['127']={'イルミ','Ilmi'}, ['128']={'シャリエ','Charlier'},
  ['129']={'アズリナ','Azrina'}, ['13']={'オルデンブルク','Oldenburg'}, ['130']={'シルエナ','Sylena'}, ['131']={'フェイラン','Feilan'}, ['132']={'ソロンゴ','Solongo'}, ['133']={'イスマヤ','Ismaya'}, ['134']={'カトルン','Qatrun'}, ['135']={'アマノハラ','Amanohara'},
  ['136']={'アルンゲレル','Arungarel'}, ['137']={'ハゼル','Hazel'}, ['138']={'ジンツァイ','Jinzai'}, ['139']={'キサラベ','Kisarabe'}, ['14']={'グライフ','Greif'}, ['140']={'ガンソリア','Gansoria'}, ['141']={'バイカ','Baikha'}, ['142']={'ヘイレン','Heilian'},
  ['143']={'ケイメイ','Keimei'}, ['144']={'カラムジン','Karamzin'}, ['145']={'シャンメイリ','Shan Meili'}, ['146']={'ミナトリヤ','Minatoriya'}, ['147']={'ヤルティ','Yalti'}, ['148']={'ソウウン','Songyun'}, ['149']={'アクバーザード','Akbarzadeh'}, ['15']={'グランヴィル','Granville'},
  ['150']={'アヤズィ','Ayazi'}, ['151']={'アルスラナーニ','Arslanani'}, ['152']={'イナルルクマン','Inal Rukhman'}, ['153']={'エルデマーニ','Erdemani'}, ['154']={'エルデムシャー','Erdemshah'}, ['155']={'オズグルシャー','Ozgurshah'}, ['156']={'オルズ','Oruz'}, ['157']={'オルハンデア','Orhandea'},
  ['158']={'カーヴェシュリ','Kavehshiri'}, ['159']={'カイマズ','Qaymaz'}, ['16']={'クリフォード','Clifford'}, ['160']={'ガファリハン','Ghaffar-Khan'}, ['161']={'カラダラーン','Karadaran'}, ['162']={'ギュルベク','Gulbek'}, ['163']={'クルナズ','Kurnaz'}, ['164']={'ケルマンベク','Kermanbek'},
  ['165']={'ゴルケスキン','Gorkeskin'}, ['166']={'サマルテキン','Samal Tegin'}, ['167']={'ジェミールザード','Cemilzade'}, ['168']={'ジェラーレディン','Jalal ad-Din'}, ['169']={'シャデガーン','Shadegan'}, ['17']={'グントラム','Guntram'}, ['170']={'シャヒンザード','Shahinzade'}, ['171']={'セリムプール','Selimpur'},
  ['172']={'セルクン','Selkun'}, ['173']={'ソーラブ','Sohrab'}, ['174']={'ゾラーニ','Zolani'}, ['175']={'ダルシュ','Darush'}, ['176']={'チャガルール','Chagarl'}, ['177']={'ティムラン','Timurlan'}, ['178']={'テミュルカーン','Temur Khan'}, ['179']={'デミラード','Demirad'},
  ['18']={'シャルネー','Scharne'}, ['180']={'トゥランダール','Turandar'}, ['181']={'ナーズム','Nazm'}, ['182']={'ヌルベディ','Nurbedi'}, ['183']={'ネジャーティン','Nejattin'}, ['184']={'バハド','Bahad'}, ['185']={'バハドゥルザード','Bahadurzade'}, ['186']={'バフティヤルリ','Bakhtiyarli'},
  ['187']={'バラクール','Baraqur'}, ['188']={'パルヴィザーニ','Parvizani'}, ['189']={'フェールドール','Felldor'}, ['19']={'シュヴァルツェンベルク','Schwarzenberg'}, ['190']={'フェリダンリ','Feridanli'}, ['191']={'フラムザード','Faramzade'}, ['192']={'ボズクール','Bozkurt'}, ['193']={'ミルザーン','Mirzan'},
  ['194']={'メフメターン','Mehmetan'}, ['195']={'メフルザード','Mehrzad'}, ['196']={'ヤマンテュルク','Yaman Turk'}, ['197']={'ラシデン','Rashiden'}, ['198']={'レヴァン','Revan'}, ['199']={'エルデライン','Erdelein'}, ['2']={'アーレンヴァルト','Ahrenwald'}, ['20']={'ダムヴィル','Damville'},
  ['200']={'オランド','Orlande'}, ['201']={'キアン','Kian'}, ['202']={'キース','Keith'}, ['203']={'キルフォーン','Kilforn'}, ['204']={'ギルベルト','Gilbert'}, ['205']={'コンテ','Conte'}, ['206']={'ザイホフ','Seihof'}, ['207']={'ザンデ','Zandeh'},
  ['208']={'シュヴァルベ','Schwalbe'}, ['209']={'ジラル','Girard'}, ['21']={'タルボット','Talbot'}, ['210']={'ダウム','Daum'}, ['211']={'バーグ','Berg'}, ['212']={'パルク','Parque'}, ['213']={'ファベイラ','Favela'}, ['214']={'ファン','Van'},
  ['215']={'フレーゲ','Frege'}, ['216']={'フロレンツ','Florenz'}, ['217']={'ルシュ','Leuche'}, ['218']={'ルシュ＝ベレ','Lushbere'}, ['219']={'レーリアン','Lerian'}, ['22']={'デヴルー','Devreux'}, ['220']={'レフェール','Lefèvre'}, ['221']={'ウンダーシュタント','Unterstandt'},
  ['222']={'リンデ','Lynde'}, ['223']={'エリス','Ellis'}, ['224']={'ボーゼルバイン','Bozelbyne'}, ['225']={'トーナル','Thornal'}, ['226']={'ゼマイン','Zemain'}, ['227']={'ガッツェン','Gatzen'}, ['23']={'ド・クレシー','de Crécy'}, ['24']={'ネヴィル','Neville'},
  ['25']={'パーシー','Percy'}, ['26']={'ファルケンベルク','Falkenberg'}, ['27']={'フィッツアラン','Fitzalan'}, ['28']={'フィッツウェアン','Fitzwaren'}, ['29']={'フィッツハーディング','Fitzharding'}, ['3']={'アイゼンハルト','Eisenhardt'}, ['30']={'フィッツロイ','Fitzroy'}, ['31']={'ブルクハルト','Burkhardt'},
  ['32']={'ベルクール','Belcourt'}, ['33']={'ベルクシュトラーサー','Bergstrasser'}, ['34']={'ホーエンヴァルト','Hohenwald'}, ['35']={'ホーエンシュタイン','Hohenstein'}, ['36']={'ボーフォート','Beaufort'}, ['37']={'ボーモン','Beaumont'}, ['38']={'マーミオン','Marmion'}, ['39']={'マレット','Mallet'},
  ['4']={'アデラール','Adelard'}, ['40']={'モーヴェッサン','Mauvesin'}, ['41']={'モーティマー','Mortimer'}, ['42']={'モンゴメリー','Montgomery'}, ['43']={'ライトゲープ','Leitgeb'}, ['44']={'リンデンホルスト','Lindenhorst'}, ['45']={'レイシー','Lacey'}, ['46']={'レーヴェンシュタイン','Lowenstein'},
  ['47']={'ローシュフォール','Rochefort'}, ['48']={'ローゼンベルク','Rosenberg'}, ['49']={'アイゼンバール','Eisenbar'}, ['5']={'アルデン','Alden'}, ['50']={'イェーガーバーズ','Jaegerbars'}, ['51']={'ヴァイセンパルト','Weissenpfalz'}, ['52']={'ヴァルダザール','Balthasar'}, ['53']={'ヴィルシャプール','Vilshapur'},
  ['54']={'ヴェンドルカーン','Vendulkaarn'}, ['55']={'エーデルマーフ','Edelmarf'}, ['56']={'エルヴィンスファーン','Erwinsfern'}, ['57']={'オステンラーシュ','Ostenrasch'}, ['58']={'カイゼルヴェシュト','Kaiserwest'}, ['59']={'カスハルト','Kasthardt'}, ['6']={'ヴァラモン','Valamon'}, ['60']={'クラウスダード','Klausdadt'},
  ['61']={'グリムファリード','Grimfried'}, ['62']={'クルトザル','Kurtzal'}, ['63']={'グローバーン','Grobarn'}, ['64']={'ゲオルキアス','Georgias'}, ['65']={'ゴルダシュタイン','Goldstein'}, ['66']={'ザルツナーメ','Saltnameh'}, ['67']={'シャッテンザムル','Schattensammler'}, ['68']={'シュヴァルツミーア','Schwarzmeer'},
  ['69']={'シュテルンザード','Sternzart'}, ['7']={'ヴァレンコート','Valencourt'}, ['70']={'シュトラウスヤール','Straussjahr'}, ['71']={'シュリューターバン','Schluterbahn'}, ['72']={'シルヴァフワル','Silvawald'}, ['73']={'ゾルハーシュタイン','Solharstein'}, ['74']={'ツィンマールザード','Zimmerzard'}, ['75']={'デヒテルガーム','Dechtergam'},
  ['76']={'テューリンギアシュ','Thuringiasch'}, ['77']={'ネーベルシャーヤン','Nebel Shayan'}, ['78']={'バーンバハード','Bahnbahad'}, ['79']={'ハイデファル','Heidefall'}, ['8']={'ヴィラール','Villard'}, ['80']={'ハルトマンダーフ','Hartmandorf'}, ['81']={'ハンザフワル','Hanzawald'}, ['82']={'ファリッドベルク','Faridberg'},
  ['83']={'ファルケンキールス','Falkenkirls'}, ['84']={'フォレンドーガーン','Forandogan'}, ['85']={'ブリッツァンダー','Burittzander'}, ['86']={'ブルーメンダラヤ','Blumendaraya'}, ['87']={'ブレンターザム','Brentersam'}, ['88']={'フロイデンルー','Freudenruh'}, ['89']={'ベルクメフル','Bergmehr'}, ['9']={'ヴィルヘルムンド','Wilhelmund'},
  ['90']={'ヘルムガーシャ','Helmgarscha'}, ['91']={'マーグドルン','Margdorn'}, ['92']={'ライゼンスターン','Reisenstern'}, ['93']={'ライヒパルズ','Reichpfalz'}, ['94']={'ランゲンファール','Langenfahr'}, ['95']={'リンデンナシール','Lindennasir'}, ['96']={'ルードヴァンナー','Ludwanner'}, ['97']={'レーゲンダール','Regendal'},
  ['98']={'ローゼンシャー','Rosenschar'}, ['99']={'フェイミン','Feimin'},
}

----------------------------------------------------------------- initial layout: only "0." expanded
function GT.initCollapse()
  local al=getAddressList()
  for i=0,al.Count-1 do
    local m=al.getMemoryRecord(i)
    if m.Count&gt;0 then
      local opt='' pcall(function() opt=tostring(m.Options) end)
      if not opt:find('moHideChildren') then
        local inZero=false local p=m
        while p do if p.ID==GT.ID_TOP0 then inZero=true break end p=p.Parent end
        pcall(function() m.Collapsed = not inZero end)
      end
    end
  end
end

function G.openWorldList(kind)
  local city = kind == 'spot'
  G.fresh()
  local columns = {
    {key='id', title='ID', width=125, numeric=city},
    {key='name', title=city and T('City','都市') or T('Person','人物'), width=210},
    {key='faction', title=T('Faction','勢力'), width=210},
  }
  if city then columns[#columns+1] = {key='type', title=T('Type','種類'), width=100} end
  local seen = {}
  local function append(fields, group)
    for _, field in ipairs(fields) do
      local alias = field[1]
      if not seen[alias] then
        for _, object in ipairs(G.map(kind).list) do
          local prop = UE.P(object, alias)
          if prop then
            seen[alias] = true
            local label = T(field[2], field[3])
            local title = group and (label .. ' (' .. group .. ')') or label
            columns[#columns+1] = {key=alias, property=alias, title=title,
              numeric=prop.t~='BoolProperty', width=math.max(125, math.min(260, #title*7))}
            break
          end
        end
      end
    end
  end
  local categories = city and G.SPOTCAT or G.UNITCAT
  for i, category in ipairs(categories) do
    local qualifier
    if city and (i == 2 or i == 3) then
      qualifier = i == 2 and T('base','基礎値') or T('shown','表示値')
    elseif not city and (i == 3 or i == 4) then
      qualifier = i == 3 and 'EXP' or T('level','レベル')
    end
    append(category[2], qualifier)
  end
  append(city and G.SPOTFIELDS or G.UNITFIELDS)
  local actions = {}
  local function add(en, ja, fn, single)
    actions[#actions+1] = {title=T(en,ja), single=single, run=function(rows)
      G.fresh()
      local objects = {}
      for _, row in ipairs(rows) do
        local object = G.byId(kind,row.gameId)
        if not object or object~=row.object or not UE.isLive(object) then
          GT.err('The target changed. Refresh the list.', '対象が変わりました。一覧を更新してください。')
        end
        objects[#objects+1]=object
      end
      fn(objects)
      if not single then G.refreshEditor(kind) end
    end}
  end
  add('Open in editor','エディタで開く',function(objects)
    G.setSel(kind,objects[1])
    local editor=getAddressList().getMemoryRecordByID(G.EDITOR[kind])
    if not editor.Active then editor.Active=true end
    local record=editor
    while record do record.Collapsed=false;record=record.Parent end
    getMainForm().bringToFront()
  end,true)
  if city then
    add('Seize','強奪',G.doSeize)
    add('Vacate','空白（領主を解雇）',G.doVacate)
    add('Normalize','平常化',function(objects)
      for _, object in ipairs(objects) do G.normalizeSpot(object) end
    end)
    add('Max all facilities','全施設最大',G.facilitiesMax)
    add('Change impression','印象変更',G.doSpotImpression)
  else
    local function others(fn)
      return function(objects)
        for _, object in ipairs(objects) do
          if UE.get(object,'IsPlayer') then GT.err('The player is selected.','プレイヤーが選択されています。') end
        end
        fn(objects)
      end
    end
    add('Join your faction','強制配下',others(G.doRecruit))
    add('Marry','結婚',function(objects) G.doMarry(objects[1]) end,true)
    add('Max impression','好感度最大',others(G.doImpression))
    add('Reveal details','詳細解明',G.doReveal)
    add('Max ability EXP','6能力の経験値を最大',function(objects)
      for _, object in ipairs(objects) do G.maxAbilitiesFor(object) end
    end)
  end
  UI.browse(kind, city and T('Cities','都市一覧') or T('Persons','人物一覧'), columns, function()
    G.fresh()
    local owners = city and G.spotOwners() or G.unitPowers()
    local rows = {}
    for _, object in ipairs(G.map(kind).list) do
      if city or not UE.get(object,'dv_IsDead') then
        local id = G.id(object)
        local row = {id=city and tonumber(id) or id, gameId=id, object=object, faction=G.powerName(owners[id]),
          name=city and G.spotName(id) or G.unitName(object)}
        if city then
          local spot = GT.SPOTS[(tonumber(id) or -1)+1]
          row.type = spot and L2(G.SPOTTYPE[spot[3]]) or '?'
        end
        for _, column in ipairs(columns) do
          if column.property then
            local value = UE.get(object, column.property)
            if column.numeric then value = tonumber(value)
            elseif type(value)=='boolean' then value = value and T('Yes','はい') or T('No','いいえ') end
            row[column.key] = value
          end
        end
        rows[#rows+1] = row
      end
    end
    return rows
  end, actions)
end

GT.NAMES = {
  [8000] = {'07. Lists', '07. 一覧表示'},
  [8001] = {'Cities', '都市一覧'},
  [8002] = {'Persons', '人物一覧'},
  [1] = {'[ATTACH / ALL OFF] Attach to History_Game-Win64-Shipping.exe + disable everything', '【アタッチ／一括解除】History_Game-Win64-Shipping.exeにアタッチ＋全機能オフ'},
  [2] = {'0. Settings &amp; README', '0. 設定・説明'},
  [3] = {'Language / 言語設定', '言語設定 / Language'},
  [4] = {'English (default)', 'English（既定）'},
  [5] = {'日本語 / Japanese', '日本語 / Japanese'},
  [6] = {'README', 'はじめに'},
  [7] = {'1) Start the game, LOAD A SAVE, open this table (allow its Lua script), then run [ATTACH / ALL OFF] at the top.', '1) ゲームを起動してセーブデータをロードし、このテーブルを開いて（Luaスクリプトの実行を許可）、先頭の【アタッチ／一括解除】を実行してください。'},
  [8] = {'2) Updated for Steam Build ID 26248873 (installed 2026-10-28). Game data is found by Unreal reflection (class / property names). Re-run ATTACH after a game update.', '2) 対応 Steam Build ID は 26248873（2026-10-28 導入確認）です。ゲームデータはUEリフレクション（クラス名・プロパティ名）で検出します。ゲーム更新後はアタッチを再実行してください。'},
  [9] = {'3) Orange = script (check to run / toggle), cyan = expandable group, no color = value (double-click to edit).', '3) オレンジ＝スクリプト（チェックで実行／切替）、水色＝展開できるグループ、色なし＝値（ダブルクリックで直接入力）。'},
  [10] = {'4) Messages appear in the Lua Engine window (0. &gt; Diagnostics &gt; Lua Engine).', '4) メッセージはLua Engineウィンドウに表示されます（0. ＞ 診断 ＞ Lua Engine）。'},
  [11] = {'5) Save your game before using editor / one-shot actions.', '5) 書き換え系の操作の前に、ゲーム内でセーブ（セーブデータファイルへの保存）をしてください。'},
  [12] = {'Diagnostics (only when the table does not work)', '診断（動作しないときのみ）'},
  [13] = {'Lua Engine (check = open / uncheck = close)', 'Lua Engine（チェックで開く／外すと閉じる）'},
  [14] = {'AOB scan self-test (CE is busy for a while; prints to Lua Engine)', 'AOBスキャンテスト（しばらくCEが応答しません。結果はLua Engineに表示）'},
  [15] = {'Environment / connection info (CE, process, module, build)', '環境・接続情報（CE・プロセス・モジュール・ビルド）'},
  [16] = {'Status: unresolved code / active hooks / active features', '状態：未検出コード／有効フック／有効機能'},
  [17] = {'UE reflection check (layouts, class instances, player)', 'UEリフレクション確認（構造・インスタンス数・プレイヤー）'},
  [19] = {'6) After loading another save, uncheck and re-check lists so that they point to the new data.', '6) 別のセーブデータをロードした後は、一覧のチェックを外して付け直してください（新しいデータを指すようにするため）。'},
  [20] = {'7) Wage, food flag, speed etc. are recalculated by the game; ON/OFF features rewrite them every 0.5 s.', '7) 給与・食料フラグ・速度などはゲームが再計算します。ON/OFF型の機能は0.5秒ごとに書き直します。'},
  [100] = {'01. Player', '01. プレイヤー'},
  [101] = {'Invincible (HP / health / sanity / stamina kept full, also in realtime battle)', '無敵（HP・健康・正気・スタミナを最大に維持・戦闘中も）'},
  [102] = {'Movement speed max (world map and realtime battle, multiplier)', '移動速度最大（ワールドマップ・戦闘中・倍率）'},
  [103] = {'Speed multiplier', '速度倍率'},
  [104] = {'Learn all battle skills', '全スキル習得（戦闘スキル）'},
  [105] = {'All battle skills at max level', '全スキル最大（全戦闘スキルを最大レベルで習得）'},
  [106] = {'EXP value (0 = game maximum)', '経験値（0＝ゲーム上の最大値）'},
  [107] = {'Influence max (kept at least at the value)', '影響度最大（指定値以上を維持）'},
  [108] = {'Influence value', '影響度の値'},
  [109] = {'Money: kept at least at the value', '所持金を指定値以上に維持'},
  [110] = {'Money value', '所持金の値'},
  [111] = {'Never age (age frozen)', '不老（年齢を固定）'},
  [112] = {'Player editor', 'プレイヤーエディタ'},
  [113] = {'All technologies acquired (your country)', '技術全習得（自国）'},
  [115] = {'Extra: six abilities EXP max (Leadership etc.)', '追加機能：6能力（統率など）の経験値を最大'},
  [200] = {'02. Army', '02. 部隊'},
  [201] = {'Army cost 0 (total wage kept 0)', '部隊コスト0（給与合計を0に維持）'},
  [205] = {'Morale value', '士気の値'},
  [206] = {'Army movement speed (mobility multiplier)', '部隊の移動速度最大（機動力の倍率）'},
  [207] = {'Mobility multiplier', '機動力倍率'},
  [208] = {'Army invincible (soldiers HP / stamina / morale max, food sufficient and never decreases)', '部隊無敵（兵士のHP・スタミナ・士気を最大／食料充足・食料減らない）'},
  [209] = {'Add soldiers to your army (type -&gt; number)...', '部隊に兵士追加（兵種→人数）...'},
  [211] = {'Change soldier type (several types at once)...', '部隊の兵種変更（複数兵種まとめて可）...'},
  [212] = {'All soldiers: set level', '全兵士のレベルを設定'},
  [213] = {'Soldier level', '兵士レベル'},
  [214] = {'Army editor', '部隊エディタ'},
  [215] = {'Soldier editor', '兵士エディタ'},
  [300] = {'03. Cities', '03. 都市'},
  [301] = {'Seize cities (put them under your rule; several allowed)...', '都市強奪（指定都市を自分の統治下へ・複数可）...'},
  [302] = {'Normalize cities (devastation 0, safety max, walls max; choose cities)', '都市平常化（戦争荒廃0・治安MAX・城壁最大／都市を選択）'},
  [303] = {'Wall value (minimum; wall level x multiplier is used when larger)', '城壁の値（最低値。城壁レベル×倍率の方が大きければそちら）'},
  [304] = {'Build all facilities (own cities)', '都市全施設建築（自領）'},
  [305] = {'All facilities max level (own cities)', '都市全施設最大（自領）'},
  [306] = {'Level override (0 = MaxLevel of each facility)', 'レベル指定（0＝各施設の最大レベル）'},
  [307] = {'Development max (commerce / agriculture / loyalty 100 / safety 100 / war devastation 0 / food surplus / manpower; choose cities)', '内政最大（商業・農業・忠誠100・治安100・戦争荒廃0・食料余剰・人的資源／都市を選択）'},
  [308] = {'Commerce', '商業'},
  [309] = {'Agriculture', '農業'},
  [310] = {'Keep own cities normalized (uses the wall value above)', '都市平常化を維持（上の城壁の値を使用）'},
  [311] = {'City editor', '都市エディタ'},
  [312] = {'Vacate cities (dismiss the current lord; several allowed)...', '都市空白（現在の領主を強制解雇・複数可）...'},
  [313] = {'Cities\' impression of you (several cities, value 0-100)...', '都市のプレイヤーへの印象変更（複数都市・値0～100）...'},
  [314] = {'Food surplus', '食料余剰'},
  [315] = {'Manpower', '人的資源'},
  [320] = {'Selected city (select, then edit the values below)', '選択中の都市（選択してから下の値を編集）'},
  [321] = {'Select: city shown on screen (open the city screen first)', '選択：画面に表示中の都市（先に都市画面を開く）'},
  [322] = {'Select: city you are staying in', '選択：滞在中の都市'},
  [323] = {'Select: search by name...', '選択：名前で検索...'},
  [324] = {'Selected city: values by category (check to show; follows the selection)', '選択中の都市：分類別の値（チェックで表示・選択に追従）'},
  [325] = {'Selected city: seize', '選択中の都市：強奪'},
  [326] = {'Selected city: vacate (dismiss the lord)', '選択中の都市：空白（領主を解雇）'},
  [327] = {'Selected city: normalize (uses the wall value of the item below)', '選択中の都市：平常化（下の「都市平常化」の城壁の値を使用）'},
  [328] = {'Selected city: all facilities max', '選択中の都市：全施設最大'},
  [329] = {'Selected city: impression of you...', '選択中の都市：印象変更...'},
  [400] = {'04. Items', '04. アイテム'},
  [401] = {'Item quantity editor', 'アイテム個数エディタ'},
  [402] = {'Add items: quantities', 'アイテム追加：追加個数'},
  [403] = {'Add trade goods / food (several allowed, with quantity)...', '交易品・食料を追加（複数可・個数指定）...'},
  [500] = {'05. Persons', '05. 人物'},
  [501] = {'Force persons to join your faction (several allowed)...', '指定人物強制配下（複数可）...'},
  [502] = {'Marry a person...', '指定人物結婚...'},
  [503] = {'All vassals: impression of you max', '配下人物好感度最大'},
  [504] = {'Chosen persons: impression of you max (several allowed)...', '指定人物好感度最大（複数可）...'},
  [505] = {'Person parameters (check, then pick persons)', '人物パラメーター操作（チェック後に人物を選択・複数可）'},
  [506] = {'Reveal all persons\' hidden parameters', '全人物の非公開パラメーターを公開'},
  [507] = {'Reveal details of chosen persons (abilities / characteristics; several allowed)...', '指定人物の詳細解明（能力・特性を公開・複数可）...'},
  [520] = {'Selected person (select, then edit the values below)', '選択中の人物（選択してから下の値を編集）'},
  [521] = {'Select: person shown on screen (open the person screen first)', '選択：画面に表示中の人物（先に人物画面を開く）'},
  [522] = {'Select: search by name...', '選択：名前で検索...'},
  [523] = {'Select: player', '選択：プレイヤー'},
  [524] = {'Selected person: values by category (check to show; follows the selection)', '選択中の人物：分類別の値（チェックで表示・選択に追従）'},
  [525] = {'Selected person: join your faction', '選択中の人物：強制配下'},
  [526] = {'Selected person: marry', '選択中の人物：結婚'},
  [527] = {'Selected person: impression of you max', '選択中の人物：好感度最大'},
  [528] = {'Selected person: reveal details', '選択中の人物：詳細解明'},
  [529] = {'Selected person: six abilities EXP max (value of item 1. &gt; six abilities)', '選択中の人物：6能力の経験値を最大（値は「1. プレイヤー」の6能力の項目）'},
  [600] = {'08. Other', '08. その他'},
  [601] = {'World collapse gauge 0 (division / hatred kept 0)', '世界崩壊度0（分断・憎悪を0に維持）'},
  [602] = {'Date: set / freeze (values are the live in-game date; edit to change)', '日付指定・固定（値はゲーム内の現在日付。書き換えると即反映）'},
  [604] = {'Year', '年'},
  [605] = {'Month', '月'},
  [606] = {'Day', '日'},
  [607] = {'Hour', '時'},
  [609] = {'Freeze the date (time stops; edits above are kept)', '日付を固定（時間が止まる。上の値の書き換えは有効）'},
  [610] = {'Unlock the whole map (all cities visited, discovery spots)', 'マップ全解除（全都市を訪問済み・発見地点）'},
  [611] = {'Country parameters (check to list)', '国家パラメーター操作（チェックで一覧表示）'},
  [612] = {'DataTable editor (check, then pick a table and a row)', 'データテーブル編集（チェック後に表と行を選択）'},
  [613] = {'Object browser (check, then pick a class and an object)', 'オブジェクト閲覧・編集（チェック後にクラスとオブジェクトを選択）'},
  [615] = {'Unlock all Steam achievements', 'Steam実績全解除'},
  [614] = {'Continue wars (war fatigue kept 0: your country and its enemies; no forced peace at 100)', '継戦（戦争疲弊を0に維持：自国と交戦相手国・疲弊100による強制講和を防ぐ）'},
  [700] = {'06. Realtime battle', '06. リアルタイム戦闘'},
  [701] = {'Weaken enemies (enemy HP 1 or less; siege of an enemy spot: walls 1, residents resistance 1, stockpiled food 1)', '敵弱体化（敵のHP1以下／敵領地の包囲戦：城壁1・住民抵抗1・備蓄食料1）'},
  [702] = {'Battle units: HP of you / your soldiers / enemies (check to list, during a battle)', '戦闘中の兵士一覧：あなた・自軍・敵軍のHP（戦闘中にチェックで表示）'},
}

----------------------------------------------------------------- Steam achievements (current game's own Steam API)
local function steamBool(v) return type(v)=='number' and (v&amp;0xFF)~=0 end
function GT.steamAchievementExports(module)
  local base,size=GT.mod(module),GT.modSize(module)
  if not base or not size or size&lt;=0 then GT.err('Steam API is not loaded. Start the game through Steam.','Steam APIが読み込まれていません。Steamからゲームを起動してください。') end
  local function at(rva,n)
    assert(type(rva)=='number' and rva&gt;=0 and rva+(n or 4)&lt;=size,'Invalid Steam API export layout')
    return base+rva
  end
  local function uint(rva)
    local n=readInteger(at(rva,4));assert(n,'Unreadable Steam API export layout');return n&amp;0xFFFFFFFF
  end
  local nt=uint(0x3C)
  assert(readSmallInteger(base)==0x5A4D and uint(nt)==0x4550 and readSmallInteger(at(nt+0x18,2))==0x20B,'Invalid Steam API PE header')
  local dir,dirSize=uint(nt+0x88),uint(nt+0x8C)
  at(dir,40);assert(dir&gt;0 and dirSize&gt;0,'Missing Steam API export directory')
  local functions,names=uint(dir+0x14),uint(dir+0x18)
  assert(functions&gt;0 and functions&lt;=65536 and names&gt;0 and names&lt;=65536,'Invalid Steam API export count')
  local ft,ntable,ord=uint(dir+0x1C),uint(dir+0x20),uint(dir+0x24)
  at(ft,functions*4);at(ntable,names*4);at(ord,names*2)
  local exports={}
  for i=0,names-1 do
    local nameRva=uint(ntable+i*4)
    local name=readString(at(nameRva,1),math.min(256,size-nameRva),false)
    if name and name:find('SteamAPI_',1,true)==1 then
      local index=readSmallInteger(at(ord+i*2,2))
      assert(index and (index&amp;0xFFFF)&lt;functions,'Invalid Steam API export ordinal')
      local rva=uint(ft+(index&amp;0xFFFF)*4)
      if rva&gt;0 and not (rva&gt;=dir and rva&lt;dir+dirSize) then exports[name]=at(rva,1) end
    end
  end
  return exports,base,size
end

function GT.steamAchievementsApi()
  local changed=GT.attachGame()
  if changed then GT.forgetProcess() end
  local module='steam_api64.dll'
  local exports,base,size=GT.steamAchievementExports(module)
  local statsExport=exports.SteamAPI_SteamUserStats_v013 and 'SteamAPI_SteamUserStats_v013' or 'SteamAPI_SteamUserStats_v012'
  local names={stats=statsExport,utils='SteamAPI_SteamUtils_v010',app='SteamAPI_ISteamUtils_GetAppID',
    count='SteamAPI_ISteamUserStats_GetNumAchievements',name='SteamAPI_ISteamUserStats_GetAchievementName',
    get='SteamAPI_ISteamUserStats_GetAchievement',set='SteamAPI_ISteamUserStats_SetAchievement',store='SteamAPI_ISteamUserStats_StoreStats'}
  local api={}
  for key,name in pairs(names) do
    local a=exports[name]
    if not a or a&lt;base or a&gt;=base+size then GT.err('Steam API export unavailable: '..name,'Steam API関数を取得できません: '..name) end
    api[key]=a
  end
  api.pid=getOpenedProcessID()
  function api.call(key,...)
    if getOpenedProcessID()~=api.pid or getProcessIDFromProcessName(GT.EXE)~=api.pid then GT.err('Game process changed. Attach again.','ゲームプロセスが変わりました。再アタッチしてください。') end
    local result=executeCodeEx(0,5000,assert(api[key]),...)
    if type(result)~='number' then
      api.uncertain=true
      GT.err('Steam API call did not complete: '..key..'. Result is unknown; no automatic retry.','Steam API呼び出しが完了しませんでした: '..key..'。結果は不明です。自動再試行は行いません。')
    end
    return result
  end
  local utils=api.call('utils')
  if utils==0 or (api.call('app',utils)&amp;0xFFFFFFFF)~=2953520 then GT.err('Steam AppID does not match The End of History (2953520).','Steam AppIDがThe End of History（2953520）と一致しません。') end
  api.instance=api.call('stats')
  if api.instance==0 then GT.err('Steam user stats are not ready.','Steam実績情報が準備できていません。') end
  return api
end

local function steamAchievementRead(api,items)
  local buf=assert(allocateMemory(16),'Steam achievement read buffer unavailable')
  local ok,result=xpcall(function()
    for _,item in ipairs(items) do
      writeBytes(buf,0xFE)
      local good=steamBool(api.call('get',api.instance,{type=3,value=item.name},buf))
      local value=readBytes(buf,1)
      if not good or (value~=0 and value~=1) then GT.err('Steam achievement data not ready: '..item.name,'Steam実績情報を読み取れません: '..item.name) end
      item.unlocked=(value==1)
    end
    return items
  end,tostring)
  -- A timed-out call could still be using the buffer. Retain it until process exit.
  if not api.uncertain then deAlloc(buf) end
  if not ok then error(result,0) end
  return result
end

function GT.steamAchievementsPlan()
  local api=GT.steamAchievementsApi()
  local n=api.call('count',api.instance)&amp;0xFFFFFFFF
  if n==0 or n&gt;4096 then GT.err('Steam achievement list is empty or invalid. Wait for Steam to finish loading.','Steam実績一覧が空、または無効です。Steamの読み込み完了後に実行してください。') end
  local thunk=assert(allocateMemory(0x1000),'Steam name copier allocation failed')
  local buf=assert(allocateMemory(512),'Steam name buffer allocation failed')
  local items,seen={},{}
  local ok,result=xpcall(function()
    local code=([==[
%X:
  push rbx
  sub rsp,20
  mov rbx,r8
  call r9
  mov rdx,rax
  xor eax,eax
  test rdx,rdx
  jz +32
  xor ecx,ecx
@@:
  mov al,[rdx+rcx]
  mov [rbx+rcx],al
  test al,al
  jz @f
  inc ecx
  cmp ecx,1FF
  jb @b
  mov byte ptr [rbx+1FF],0
  xor eax,eax
  jmp +5
@@:
  mov eax,1
  add rsp,20
  pop rbx
  ret
]==]):format(thunk)
    -- Named local labels avoid instruction-size-dependent branch displacements.
    code=code:gsub('jz %+32','jz eoh_name_done'):gsub('jmp %+5','jmp eoh_name_done')
    code='label(eoh_name_done)\n'..code:gsub('  add rsp,20','eoh_name_done:\n  add rsp,20')
    assert(autoAssemble(code),'Steam name copier assembly failed')
    api.copyName=thunk
    for i=0,n-1 do
      writeBytes(buf,0)
      local copied=api.call('copyName',api.instance,i,buf,api.name)
      local name=steamBool(copied) and readString(buf,512,false)
      if not name or #name==0 or #name&gt;=512 or seen[name] then GT.err('Steam achievement list is incomplete or ambiguous.','Steam実績一覧が不完全、または重複しています。') end
      seen[name]=true;items[#items+1]={name=name}
    end
    return items
  end,tostring)
  -- A timed-out helper may still access either allocation; keep them until process exit.
  if not api.uncertain then deAlloc(buf);deAlloc(thunk) end
  if not ok then error(result,0) end
  steamAchievementRead(api,items)
  return api,items
end

function GT.steamUnlockAll()
  if GT.steamAchievementBusy then GT.err('Steam achievement action is already running.','Steam実績処理はすでに実行中です。') end
  GT.steamAchievementBusy=true
  local ok,result=xpcall(function()
    local api,items=GT.steamAchievementsPlan()
    local previous,updated,failed=0,0,{}
    for _,item in ipairs(items) do
      if item.unlocked then previous=previous+1
      elseif steamBool(api.call('set',api.instance,{type=3,value=item.name})) then updated=updated+1
      else failed[#failed+1]=item.name end
    end
    -- Submit once after the batch, including a retry after a previous failed submission.
    local stored=steamBool(api.call('store',api.instance))
    steamAchievementRead(api,items)
    local verified=0 for _,item in ipairs(items) do if item.unlocked then verified=verified+1 end end
    GT.msg(('Steam achievements: local %d/%d; already unlocked %d, newly accepted %d, rejected %d.'):format(verified,#items,previous,updated,#failed),
      ('Steam実績: ローカル解除 %d/%d件、解除済み %d件、新規受付 %d件、拒否 %d件。'):format(verified,#items,previous,updated,#failed))
    if not stored then GT.err('Steam rejected the save request. Local changes may be pending; retry later.','Steamへの保存要求が拒否されました。ローカル変更は未同期の可能性があります。時間を置いて再実行してください。') end
    if #failed&gt;0 or verified~=#items then GT.err('Some achievements were not unlocked; Steam sync was requested for accepted changes.','解除できなかった実績があります。受け付けられた変更についてSteam同期を要求しました。') end
    GT.msg('Steam sync requested. Server completion has not been confirmed; check your Steam achievement list.',
      'Steam同期を要求しました。サーバーでの完了は未確認です。Steamの実績一覧で確認してください。')
  end,tostring)
  GT.steamAchievementBusy=false
  if not ok then error(result,0) end
end

function GT.steamUnlockAction(mr)
  local ok,err=pcall(GT.steamUnlockAll)
  if not ok then GT.msg('Steam achievements: '..tostring(err),'Steam実績: '..tostring(err)) end
  local timer=createTimer(nil,false);timer.Interval=150
  timer.OnTimer=function(t) t.destroy();pcall(function() mr.Active=false end) end
  timer.Enabled=true
end

pcall(GT.initCollapse)
GT.applyLang()
</LuaScript>
</CheatTable>
