using System; using System.Collections.Generic; using System.IO; using System.Linq; using DatReaderWriter; using DatReaderWriter.DBObjs; // Dumps the retail-default keymap (gmDefaultMap @ 0x14000000) from // client_portal.dat. Used for the Phase K control overhaul — extracts // the canonical "default bindings" so we can ship a Retail preset // that exactly matches what AC1 players were trained on. string datDir = args.Length > 0 ? args[0] : Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile), "Documents", "Asheron's Call"); if (!Directory.Exists(datDir)) { Console.Error.WriteLine($"dat dir not found: {datDir}"); return 1; } Console.WriteLine($"# Reading dats from: {datDir}"); using var dat = new DatCollection(datDir); // gmDefaultMap is at 0x14000000 per DatReaderWriter test // CanReadEORKeymaps. The test also references 0x14000002 (unnamed). foreach (uint id in new uint[] { 0x14000000u, 0x14000002u }) { Console.WriteLine(); Console.WriteLine($"## MasterInputMap 0x{id:X8}"); var map = dat.Get(id); if (map is null) { Console.WriteLine($" (not found)"); continue; } Console.WriteLine($" Name: {map.Name}"); Console.WriteLine($" GuidMap: {map.GuidMap}"); Console.WriteLine($" Devices: {map.Devices.Count}"); foreach (var d in map.Devices) { Console.WriteLine($" {d.Type} {d.Guid}"); } Console.WriteLine($" MetaKeys ({map.MetaKeys.Count}):"); foreach (var mk in map.MetaKeys) { var (scan, dev) = SplitKey(mk.Key); Console.WriteLine($" {Dik(scan),-12} (scan=0x{scan:X2}, dev={dev}) ModifierFlag=0x{mk.Modifier:X8}"); } Console.WriteLine($" InputMaps ({map.InputMaps.Count}):"); // Sort contexts by ID for stable output var sortedCtx = new List(map.InputMaps.Keys); sortedCtx.Sort(); foreach (var ctxId in sortedCtx) { var inputMap = map.InputMaps[ctxId]; Console.WriteLine(); Console.WriteLine($" Context 0x{ctxId:X8} ({inputMap.Mappings.Count} bindings):"); // Sort bindings by scan code for readability var sortedBindings = new List(inputMap.Mappings); sortedBindings.Sort((a, b) => a.Key.Key.CompareTo(b.Key.Key)); foreach (var m in sortedBindings) { var (scan, dev) = SplitKey(m.Key.Key); string mods = ModifierString(m.Key.Modifier); Console.WriteLine( $" {Dik(scan),-12}{(mods.Length > 0 ? "+" + mods : " ")} " + $"(scan=0x{scan:X2}, dev={dev}) " + $"Action=0x{m.Unknown:X8} " + $"Activation=0x{m.Activation:X2}"); } } } return 0; // ── Key decoding (scan code in high word, device id in low word) ────── static (uint scan, uint dev) SplitKey(uint key) => ((key >> 16) & 0xFFFFu, key & 0xFFFFu); // ── Modifier flag bitfield ──────────────────────────────────────────── static string ModifierString(uint flag) => flag == 0 ? "" : string.Join("|", new[] { (flag & 0x80000000u) != 0 ? "Shift" : null, (flag & 0x40000000u) != 0 ? "Ctrl" : null, (flag & 0x20000000u) != 0 ? "Alt" : null, (flag & 0x10000000u) != 0 ? "Win" : null, (flag & 0x08000000u) != 0 ? "Meta4" : null, (flag & 0x04000000u) != 0 ? "Meta5" : null, }.Where(s => s is not null)); // ── DirectInput scan-code → name (DIK_*) ────────────────────────────── // // Verified subset against acclient_2013_pseudo_c.txt // ControlNameMapper::AddKeySemantic calls (lines 656172-656272). Rest // from standard Microsoft DirectInput dinput.h DIK_* table. static string Dik(uint code) => code switch { 0x01 => "ESCAPE", 0x02 => "1", 0x03 => "2", 0x04 => "3", 0x05 => "4", 0x06 => "5", 0x07 => "6", 0x08 => "7", 0x09 => "8", 0x0a => "9", 0x0b => "0", 0x0c => "MINUS", 0x0d => "EQUALS", 0x0e => "BACK", 0x0f => "TAB", 0x10 => "Q", 0x11 => "W", 0x12 => "E", 0x13 => "R", 0x14 => "T", 0x15 => "Y", 0x16 => "U", 0x17 => "I", 0x18 => "O", 0x19 => "P", 0x1a => "LBRACKET", 0x1b => "RBRACKET", 0x1c => "RETURN", 0x1d => "LCONTROL", 0x1e => "A", 0x1f => "S", 0x20 => "D", 0x21 => "F", 0x22 => "G", 0x23 => "H", 0x24 => "J", 0x25 => "K", 0x26 => "L", 0x27 => "SEMICOLON", 0x28 => "APOSTROPHE", 0x29 => "GRAVE", 0x2a => "LSHIFT", 0x2b => "BACKSLASH", 0x2c => "Z", 0x2d => "X", 0x2e => "C", 0x2f => "V", 0x30 => "B", 0x31 => "N", 0x32 => "M", 0x33 => "COMMA", 0x34 => "PERIOD", 0x35 => "SLASH", 0x36 => "RSHIFT", 0x37 => "MULTIPLY", 0x38 => "LMENU", 0x39 => "SPACE", 0x3a => "CAPITAL", 0x3b => "F1", 0x3c => "F2", 0x3d => "F3", 0x3e => "F4", 0x3f => "F5", 0x40 => "F6", 0x41 => "F7", 0x42 => "F8", 0x43 => "F9", 0x44 => "F10", 0x45 => "NUMLOCK", 0x46 => "SCROLL", 0x47 => "NUMPAD7", 0x48 => "NUMPAD8", 0x49 => "NUMPAD9", 0x4a => "SUBTRACT", 0x4b => "NUMPAD4", 0x4c => "NUMPAD5", 0x4d => "NUMPAD6", 0x4e => "ADD", 0x4f => "NUMPAD1", 0x50 => "NUMPAD2", 0x51 => "NUMPAD3", 0x52 => "NUMPAD0", 0x53 => "DECIMAL", 0x57 => "F11", 0x58 => "F12", 0x9c => "NUMPADENTER", 0x9d => "RCONTROL", 0xb5 => "DIVIDE", 0xb7 => "SYSRQ", 0xb8 => "RMENU", 0xc7 => "HOME", 0xc8 => "UP", 0xc9 => "PRIOR" /*PgUp*/, 0xcb => "LEFT", 0xcd => "RIGHT", 0xcf => "END", 0xd0 => "DOWN", 0xd1 => "NEXT" /*PgDn*/, 0xd2 => "INSERT", 0xd3 => "DELETE", _ => $"?0x{code:X2}", }; // ── Action ID → name ────────────────────────────────────────────────── // // Extracted verbatim from acclient_2013_pseudo_c.txt // command_strings table at 0x00803df0 (lines 1067906-1068117). // Subset — covers indices 0x000-0x0d5; the rest are mostly emote // variants (Cheer, ChestBeat, FallDown, etc.) we don't need for // keymap analysis. static string Action(uint id) => id switch { 0x000 => "Invalid", 0x001 => "HoldRun", 0x002 => "HoldSidestep", 0x003 => "Ready", 0x004 => "Stop", 0x005 => "WalkForward", 0x006 => "WalkBackwards", 0x007 => "RunForward", 0x008 => "Fallen", 0x009 => "Interpolating", 0x00a => "Hover", 0x00d => "TurnRight", 0x00e => "TurnLeft", 0x00f => "SideStepRight", 0x010 => "SideStepLeft", 0x011 => "Dead", 0x012 => "Crouch", 0x013 => "Sitting", 0x014 => "Sleeping", 0x015 => "Falling", 0x018 => "Pickup", 0x01d => "JumpCharging", 0x03b => "Jump", 0x03c => "HandCombat", 0x03d => "NonCombat", 0x0a2 => "Cancel", 0x0a3 => "UseSelected", 0x0a4 => "AutosortSelected", 0x0a5 => "DropSelected", 0x0a6 => "GiveSelected", 0x0a7 => "SplitSelected", 0x0a8 => "ExamineSelected", 0x0a9 => "CreateShortcutToSelected", 0x0aa => "PreviousCompassItem", 0x0ab => "NextCompassItem", 0x0ac => "ClosestCompassItem", 0x0ad => "PreviousSelection", 0x0ae => "LastAttacker", 0x0af => "PreviousFellow", 0x0b0 => "NextFellow", 0x0b1 => "ToggleCombat", 0x0b2 => "HighAttack", 0x0b3 => "MediumAttack", 0x0b4 => "LowAttack", 0x0b5 => "EnterChat", 0x0b6 => "ToggleChat", 0x0b7 => "SavePosition", 0x0b8 => "OptionsPanel", 0x0b9 => "ResetView", 0x0ba => "CameraLeftRotate", 0x0bb => "CameraRightRotate", 0x0bc => "CameraRaise", 0x0bd => "CameraLower", 0x0be => "CameraCloser", 0x0bf => "CameraFarther", 0x0c0 => "FloorView", 0x0c1 => "MouseLook", 0x0c2 => "PreviousItem", 0x0c3 => "NextItem", 0x0c4 => "ClosestItem", 0x0c5 => "ShiftView", 0x0c6 => "MapView", 0x0c7 => "AutoRun", 0x0c8 => "DecreasePowerSetting", 0x0c9 => "IncreasePowerSetting", 0x0d3 => "CastSpell", 0x0d5 => "FirstPersonView", _ => $"?0x{id:X4}", };