Created
February 3, 2025 14:50
-
-
Save Kattoor/1e6290f3af39aa97e7c242c04585a39f to your computer and use it in GitHub Desktop.
Arcanist deserializer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fs from 'fs'; | |
import createKbuffer from "./kbuffer.js"; | |
import pg from 'pg'; | |
const {Pool} = pg; | |
const pool = new Pool({ | |
host: 'localhost', port: 5432, user: 'postgres', password: 'nope', database: 'postgres', | |
}); | |
const spellsEnum = { | |
'-2': 'Player', // 0xFFFFFFFE | |
'-1': 'None', // 0xFFFFFFFF | |
'0': 'Arcane_Arrow', | |
'1': 'Arcane_Bomb', | |
'2': 'Arcane_Tower', | |
'3': 'Arcane_Energiser', | |
'4': 'Arcane_Gate', | |
'5': 'Arcane_Portal', | |
'6': 'Summon_Imps', | |
'7': 'Imp_Destruction', | |
'8': 'Arcane_Glyph', | |
'9': 'Arcane_Sigil', | |
'10': 'Arcane_Flash', // 0x0000000A | |
'11': 'Summon_Dragon_Egg', // 0x0000000B | |
'12': 'Fire_Ball', // 0x0000000C | |
'13': 'Fire_Arrow', // 0x0000000D | |
'14': 'Lava_Bomb', // 0x0000000E | |
'15': 'Magma_Bomb', // 0x0000000F | |
'16': 'Flame_Shield', // 0x00000010 | |
'17': 'Flame_Wall', // 0x00000011 | |
'18': 'Napalm', // 0x00000012 | |
'19': 'Napalm_Bomb', // 0x00000013 | |
'20': 'Rain_of_Fire', // 0x00000014 | |
'21': 'Rain_of_Arrows', // 0x00000015 | |
'22': 'Volcano', // 0x00000016 | |
'23': 'Summon_Flame_Dragon', // 0x00000017 | |
'24': 'Pebble_Shot', // 0x00000018 | |
'25': 'Scatter_Rock', // 0x00000019 | |
'26': 'Quake', // 0x0000001A | |
'27': 'Disruption', // 0x0000001B | |
'28': 'Mud_Ball', // 0x0000001C | |
'29': 'Mega_Boulder', // 0x0000001D | |
'30': 'Rock_Slab', // 0x0000001E | |
'31': 'Fortress', // 0x0000001F | |
'32': 'Summon_Dwarf', // 0x00000020 | |
'33': 'Summon_Rock_Golem', // 0x00000021 | |
'34': 'Meteor', // 0x00000022 | |
'35': 'Fissure', // 0x00000023 | |
'36': 'Thunder_Shock', // 0x00000024 | |
'37': 'Chain_Lightning', // 0x00000025 | |
'38': 'Wind_Shield', // 0x00000026 | |
'39': 'Hurricane', // 0x00000027 | |
'40': 'Shock_Bomb', // 0x00000028 | |
'41': 'Storm_Shield', // 0x00000029 | |
'42': 'Summon_Cyclops', // 0x0000002A | |
'43': 'Conductor_Rod', // 0x0000002B | |
'44': 'Summon_Storm_Spirit', // 0x0000002C | |
'45': 'Flight', // 0x0000002D | |
'46': 'Storm', // 0x0000002E | |
'47': 'Summon_Storm_Dragon', // 0x0000002F | |
'48': 'Ice_Ball', // 0x00000030 | |
'49': 'Ice_Bomb', // 0x00000031 | |
'50': 'Frost_Shards', // 0x00000032 | |
'51': 'Frost_Arrow', // 0x00000033 | |
'52': 'Snowball', // 0x00000034 | |
'53': 'Blizzard', // 0x00000035 | |
'54': 'Ice_Shield', // 0x00000036 | |
'55': 'Ice_Castle', // 0x00000037 | |
'56': 'Summon_Sylph', // 0x00000038 | |
'57': 'Summon_Frost_Giant', // 0x00000039 | |
'58': 'Comet', // 0x0000003A | |
'59': 'Frost_Dragon', // 0x0000003B | |
'60': 'Den_of_Darkness', // 0x0000003C | |
'61': 'Rain_of_Chaos', // 0x0000003D | |
'62': 'Drain_Bolt', // 0x0000003E | |
'63': 'Death_Bomb', // 0x0000003F | |
'64': 'Summon_Swarm', // 0x00000040 | |
'65': 'Summon_Dark_Knight', // 0x00000041 | |
'66': 'Raise_Dead', // 0x00000042 | |
'67': 'Summon_Wraith', // 0x00000043 | |
'68': 'Aura_of_Decay', // 0x00000044 | |
'69': 'Dark_Defences', // 0x00000045 | |
'70': 'Swallowing_Pit', // 0x00000046 | |
'71': 'Lichdom', // 0x00000047 | |
'72': 'Protection_Shield', // 0x00000048 | |
'73': 'Sky_Ray', // 0x00000049 | |
'74': 'Shining_Bolt', // 0x0000004A | |
'75': 'Rising_Star', // 0x0000004B | |
'76': 'Summon_Pegasus', // 0x0000004C | |
'77': 'Summon_Paladin', // 0x0000004D | |
'78': 'Forest_Seed', // 0x0000004E | |
'79': 'Summon_Pixies', // 0x0000004F | |
'80': 'Sphere_of_Healing', // 0x00000050 | |
'81': 'Castle_of_Light', // 0x00000051 | |
'82': 'Invulnerability_Shield', // 0x00000052 | |
'83': 'Shining_Power', // 0x00000053 | |
'84': 'Thorn_Ball', // 0x00000054 | |
'85': 'Thorn_Bomb', // 0x00000055 | |
'86': 'Vine_Whip', // 0x00000056 | |
'87': 'Vine_Bridge', // 0x00000057 | |
'88': 'Entangle', // 0x00000058 | |
'89': 'Vine_Bomb', // 0x00000059 | |
'90': 'Summon_Man_Trap', // 0x0000005A | |
'91': 'Sanctuary', // 0x0000005B | |
'92': 'Summon_Elves', // 0x0000005C | |
'93': 'Flurry', // 0x0000005D | |
'94': 'Nature_s_Wrath', // 0x0000005E | |
'95': 'Vine_Bloom', // 0x0000005F | |
'96': 'Water_Ball', // 0x00000060 | |
'97': 'Maelstrom', // 0x00000061 | |
'98': 'Summon_Water_Trolls', // 0x00000062 | |
'99': 'Hydration', // 0x00000063 | |
'100': 'Deluge', // 0x00000064 | |
'101': 'English_Summer', // 0x00000065 | |
'102': 'Brine_Bolt', // 0x00000066 | |
'103': 'Brine_Bomb', // 0x00000067 | |
'104': 'Summon_Brine_Goblin', // 0x00000068 | |
'105': 'Rain_of_Clams', // 0x00000069 | |
'106': 'Ocean_s_Fury', // 0x0000006A | |
'107': 'Summon_Water_Lord', // 0x0000006B | |
'108': 'Static_Ball', // 0x0000006C | |
'109': 'Static_Shield', // 0x0000006D | |
'110': 'Mechanical_Arrow', // 0x0000006E | |
'111': 'Cog_Fall', // 0x0000006F | |
'112': 'Recall_Device', // 0x00000070 | |
'113': 'Calling_Bell', // 0x00000071 | |
'114': 'Clock_Tower', // 0x00000072 | |
'115': 'Cuckoo_Clock', // 0x00000073 | |
'116': 'Blast_From_The_Past', // 0x00000074 | |
'117': 'Clockwork_Bomb', // 0x00000075 | |
'118': 'Steam_Dragon', // 0x00000076 | |
'119': 'Redo', // 0x00000077 | |
'120': 'Banish', // 0x00000078 | |
'121': 'Self_Destruct', // 0x00000079 | |
'122': 'Mudruption', // 0x0000007A | |
'123': 'Mine', // 0x0000007B | |
'124': 'Kablam', // 0x0000007C | |
'125': 'Wallop', // 0x0000007D | |
'126': 'Storm_Dragon_Breath', // 0x0000007E | |
'127': 'Shock_Shield', // 0x0000007F | |
'128': 'Spirit_Hurricane', // 0x00000080 | |
'129': 'Snow', // 0x00000081 | |
'130': 'Sylph_Arrow', // 0x00000082 | |
'131': 'Charge', // 0x00000083 | |
'132': 'Zombie_Breath', // 0x00000084 | |
'133': 'Sunder', // 0x00000085 | |
'134': 'Fairy_Ring', // 0x00000086 | |
'135': 'Volley', // 0x00000087 | |
'136': 'Dive', // 0x00000088 | |
'137': 'Recall', // 0x00000089 | |
'138': 'Monkey', // 0x0000008A | |
'139': 'New_Years_Rocket', // 0x0000008B | |
'140': 'Deserving_of_Coal', // 0x0000008C | |
'141': 'Summon_Snowman', // 0x0000008D | |
'142': 'Summon_Reindeer', // 0x0000008E | |
'143': 'Presents', // 0x0000008F | |
'144': 'Christmas_Tree', // 0x00000090 | |
'145': 'Thanksgiving_Dinner', // 0x00000091 | |
'146': 'Firecrackers', // 0x00000092 | |
'147': 'Santas_Magic', // 0x00000093 | |
'148': 'Jingle_Boom', // 0x00000094 | |
'149': 'Coal', // 0x00000095 | |
'150': 'Tree_House', // 0x00000096 | |
'151': 'Firework_Show', // 0x00000097 | |
'152': 'Arcane_Dragon', // 0x00000098 | |
'153': 'Air_Surge', // 0x00000099 | |
'154': 'Apparition', // 0x0000009A | |
'155': 'Duplication', // 0x0000009B | |
'156': 'Color_Spray', // 0x0000009C | |
'157': 'Floating_Castle', // 0x0000009D | |
'158': 'Glide', // 0x0000009E | |
'159': 'Magical_Barrier', // 0x0000009F | |
'160': 'Summon_Phantom', // 0x000000A0 | |
'161': 'Vortex', // 0x000000A1 | |
'162': 'Whisper_Arrow', // 0x000000A2 | |
'163': 'Whisper_Bomb', // 0x000000A3 | |
'164': 'Social_Distancing', // 0x000000A4 | |
'165': 'Corrupt_Dragon', // 0x000000A5 | |
'166': 'Frost_Leap', // 0x000000A6 | |
'167': 'Hatch', // 0x000000A7 | |
'168': 'Meteor_Shower', // 0x000000A8 | |
'169': 'Time_Dilation', // 0x000000A9 | |
'170': 'Summon_Kraken', // 0x000000AA | |
'171': 'Summon_Ship', // 0x000000AB | |
'172': 'Kraken_Attack', // 0x000000AC | |
'173': 'From_the_Depths', // 0x000000AD | |
'174': 'Fire_Cannon', // 0x000000AE | |
'175': 'Entangle_Kraken', // 0x000000AF | |
'176': 'Summon_Mountain_Goat', // 0x000000B0 | |
'177': 'Fire_Wave', // 0x000000B1 | |
'178': 'Gift_of_Giving', // 0x000000B2 | |
'179': 'Forestation', // 0x000000B3 | |
'180': 'Electrostatic_Charge', // 0x000000B4 | |
'181': 'The_ol_swaparoo', // 0x000000B5 | |
'182': 'Whistling_Winds', // 0x000000B6 | |
'183': 'Blood_Lust', // 0x000000B7 | |
'184': 'Curse_of_Loneliness', // 0x000000B8 | |
'185': 'Blood_Mist', // 0x000000B9 | |
'186': 'Barrage_of_Bones', // 0x000000BA | |
'187': 'Flesh_Wound', // 0x000000BB | |
'188': 'Blood_Bath', // 0x000000BC | |
'189': 'Curse_of_Disabling', // 0x000000BD | |
'190': 'Summon_Blood_Bank', // 0x000000BE | |
'191': 'Summon_Gargoyle', // 0x000000BF | |
'192': 'Blood_Craze', // 0x000000C0 | |
'193': 'Summon_Vampire', // 0x000000C1 | |
'194': 'Infection', // 0x000000C2 | |
'195': 'Blood_Pact', // 0x000000C3 | |
'196': 'Dark_Totem', // 0x000000C4 | |
'197': 'Stone_Form', // 0x000000C5 | |
'198': 'Resurrection', // 0x000000C6 | |
'199': 'Arcane_Mist', // 0x000000C7 | |
'200': 'Ent_Whip', // 0x000000C8 | |
'201': 'Water_Drop', // 0x000000C9 | |
'202': 'Blink', // 0x000000CA | |
'203': 'HighHigh', // 0x000000CB | |
'204': 'PowerOfLight', // 0x000000CC | |
'205': 'Little_Devil', // 0x000000CD | |
'206': 'Acid_Rain', // 0x000000CE | |
'207': 'Arcane_Meteor', // 0x000000CF | |
'208': 'Arcane_Meteor_Shard', // 0x000000D0 | |
'209': 'Stepping_Stone', // 0x000000D1 | |
'210': 'Summon_Tutorial_Target', // 0x000000D2 | |
'211': 'Call_of_the_Void', // 0x000000D3 | |
'212': 'Pocket_Sand', // 0x000000D4 | |
'213': 'Summon_King_Monarch', // 0x000000D5 | |
'214': 'Life_Dew', // 0x000000D6 | |
'215': 'Breeze', // 0x000000D7 | |
'216': 'Pinecone', // 0x000000D8 | |
'217': 'Erosion', // 0x000000D9 | |
'218': 'Acorn', // 0x000000DA | |
'219': 'Morning_Sun', // 0x000000DB | |
'220': 'Autumn_Leaves', // 0x000000DC | |
'221': 'The_Four_Seasons', // 0x000000DD | |
'222': 'Summon_Dryad', // 0x000000DE | |
'223': 'Seasonal', // 0x000000DF | |
'224': 'Flutter', // 0x000000E0 | |
'225': 'Chomp', // 0x000000E1 | |
'226': 'Sacrifice', // 0x000000E2 | |
'227': 'Vampire_Bat', // 0x000000E3 | |
'228': 'Melt', // 0x000000E4 | |
'229': 'Snowbolt', // 0x000000E5 | |
'230': 'Cogmobile', // 0x000000E6 | |
'231': 'Cog', // 0x000000E7 | |
'232': 'Smash', // 0x000000E8 | |
'233': 'Blood_Siphon', // 0x000000E9 | |
'234': 'Watchtower', // 0x000000EA | |
'235': 'Summon_Beehive', // 0x000000EB | |
'236': 'Miner_Market', // 0x000000EC | |
'237': 'Rock_Blaster', // 0x000000ED | |
'238': 'Shaft', // 0x000000EE | |
'239': 'Miner_Map', // 0x000000EF | |
'240': 'Summon_Bees', // 0x000000F0 | |
'241': 'Summon_Snowman2', // 0x000000F1 | |
'242': 'Snow_Globe2', // 0x000000F2 | |
'243': 'Summon_Myth', // 0x000000F3 | |
'244': 'Rusty_Bomb', // 0x000000F4 | |
'245': 'Summon_Will_o_the_Wisp', // 0x000000F5 | |
'246': 'Summon_Boar', // 0x000000F6 | |
'247': 'Summon_Tiger', // 0x000000F7 | |
'248': 'Bear_Form', // 0x000000F8 | |
'249': 'Enchanted_Axes', // 0x000000F9 | |
'250': 'Harmony', // 0x000000FA | |
'251': 'Verdant_Javelin', // 0x000000FB | |
'252': 'Faiere_Jump', // 0x000000FC | |
'253': 'Bear_Claw', // 0x000000FD | |
'254': 'Grove_Renewal', // 0x000000FE | |
'255': 'Prickly_Barrier', // 0x000000FF | |
'256': 'Healing_Spores', // 0x00000100 | |
'257': 'Bite', // 0x00000101 | |
'258': 'Rampage', // 0x00000102 | |
'259': 'Spirit_Link', // 0x00000103 | |
'260': 'Summon_Alpha_Wolf', // 0x00000104 | |
'261': 'Star_Bolt', // 0x00000105 | |
'262': 'Shooting_Stars', // 0x00000106 | |
'263': 'Gravity_Pulse', // 0x00000107 | |
'264': 'Dark_Matter_Bomb', // 0x00000108 | |
'265': 'Wormhole', // 0x00000109 | |
'266': 'Collision_Course', // 0x0000010A | |
'267': 'Fusion', // 0x0000010B | |
'268': 'MACAIR', // 0x0000010C | |
'269': 'Abduction', // 0x0000010D | |
'270': 'Cosmic_Horror', // 0x0000010E | |
'271': 'Black_Hole', // 0x0000010F | |
'272': 'Supernova', // 0x00000110 | |
'273': 'Starfire', // 0x00000111 | |
'274': 'Starfire_Shard', // 0x00000112 | |
'275': 'Summon_Drone', // 0x00000113 | |
'276': 'Drone_Strike', // 0x00000114 | |
'277': 'Tentacle_Smash', // 0x00000115 | |
'278': 'Star_Dust', // 0x00000116 | |
'279': 'Star_Ball', // 0x00000117 | |
'280': 'Swipe', // 0x00000118 | |
'281': 'Brine_Burst', // 0x00000119 | |
'282': 'Gravity_Well', // 0x0000011A | |
'283': 'Butterfly_Jar', // 0x0000011B | |
'284': 'Death_and_Decay', // 0x0000011C | |
'285': 'Spirit_Walk', // 0x0000011D | |
'286': 'Retribution', // 0x0000011E | |
'287': 'Blood_Clot', // 0x0000011F | |
'288': 'Old_Mud_Ball', // 0x00000120 | |
'289': 'Large_Bite', // 0x00000121 | |
'290': 'Pack_Mentality', // 0x00000122 | |
'291': 'Pack_Leader', // 0x00000123 | |
'292': 'Storm_Jolt', // 0x00000124 | |
'293': 'Frost_Nova', // 0x00000125 | |
'294': 'Summon_Titan', // 0x00000126 | |
'295': 'Summon_Monarchs', // 0x00000127 | |
'296': 'Pounce', // 0x00000128 | |
'297': 'Stalk', // 0x00000129 | |
'298': 'Rising_Lava', // 0x0000012A | |
'299': 'Steam_Cannon', // 0x0000012B | |
'300': 'Compete', // 0x0000012C | |
'301': 'When_Pigs_Fly', // 0x0000012D | |
'302': 'Dense_Fog', // 0x0000012E | |
'303': 'Tomato', // 0x0000012F | |
'304': 'Monolith', // 0x00000130 | |
'305': 'Summon_Wyrm', // 0x00000131 | |
'306': 'Sandbag', // 0x00000132 | |
'307': 'Sandy_Shores', // 0x00000133 | |
'308': 'Bucket_of_Sand', // 0x00000134 | |
'309': 'Pyramid', // 0x00000135 | |
'310': 'Sand_Trap', // 0x00000136 | |
'311': 'Sand_Castle', // 0x00000137 | |
'312': 'Burning_Sands', // 0x00000138 | |
'313': 'Sand_Storm', // 0x00000139 | |
'314': 'Summon_Sphinx', // 0x0000013A | |
'315': 'Mind_Control', // 0x0000013B | |
'316': 'Burrow', // 0x0000013C | |
'317': 'Consume', // 0x0000013D | |
'318': 'Spit', // 0x0000013E | |
'319': 'Pyramid_Strike', // 0x0000013F | |
'320': 'Mana', // 0x00000140 | |
'321': 'Sands_of_Time', // 0x00000141 | |
'322': 'Summon_Sand_Mite', // 0x00000142 | |
'323': 'Entomb', // 0x00000143 | |
'324': 'Tomato_Emoji', // 0x00000144 | |
'325': 'Zombie_Dragon', // 0x00000145 | |
}; | |
const inviteEnum = { | |
'-1': "Don't Mind", | |
'1': 'Invite Only', | |
'2': 'Clan', | |
'4': 'Friends', | |
'8': 'Similar Rating', | |
'16': 'Open', | |
'32768': 'BookClub' | |
}; | |
const timeEnum = { | |
'-1': "Don't Mind", | |
'32': 'Five', | |
'256': 'One Hundred Twenty', | |
'512': 'Ninety', | |
'1024': 'Sixty', | |
'2048': 'Forty Five', | |
'4096': 'Thirty', | |
'8192': 'Twenty', | |
'16384': 'Ten', | |
'262144': 'Seven', | |
'1048576': 'Fifteen' | |
}; | |
const playersPerTeamEnum = { | |
'-1': "Don't Mind", | |
'1048576': 'Eight', | |
'2097152': 'Half', | |
'33554432': 'Two', | |
'67108864': 'Three', | |
'134217728': 'Four', | |
'268435456': 'Five', | |
'536870912': 'Six', | |
'1073741824': 'Seven' | |
}; | |
const gameStyleEnum = { | |
'-1': "Don't Mind", | |
'1': 'Shuffle Players', | |
'2': 'Elementals', | |
'4': 'Custom HP', | |
'8': 'Random Spells', | |
'16': 'Original Spells Only', | |
'128': 'No Movement', | |
'256': 'Zero Shield', | |
'2048': 'Bid', | |
'4096': 'Standard', | |
'8192': 'First Turn Teleport', | |
'16384': 'Sandbox', | |
'32768': 'Zombie Monkey', | |
'65536': 'Watchtower', | |
'131072': 'Arcane Monster', | |
'262144': 'Wind' | |
}; | |
const mapEnum = { | |
'-1': "Don't Mind", | |
'16': 'Jungle', | |
'32': 'Snowy Hills', | |
'64': 'Ocean Floor', | |
'512': 'Dark Fortress', | |
'1024': 'Wasteland', | |
'32768': 'Grassy Hills', | |
'65536': 'Giants Mountain', | |
'131072': 'Elven Isles', | |
'262144': 'Goblin Caves', | |
'524288': 'Murky Swamp', | |
'1048576': 'Graveyard', | |
'2097152': 'Sky Castles', | |
'4194304': 'Mos LeHarmless', | |
'8388608': 'Arcane Crystals', | |
'16777216': 'Random', | |
'33554432': 'Alien World', | |
'67108864': 'Ghostly Halls', | |
'134217728': 'Desert', | |
'1073741824': 'Space Nexus' | |
}; | |
const playerEnum = { | |
'-1': "Don't Mind", | |
'33554432': 'Twenty Four', | |
'67108864': 'Two', | |
'134217728': 'Three', | |
'268435456': 'Four', | |
'536870912': 'Five', | |
'1073741824': 'Six' | |
}; | |
const bookEnum = { | |
'-1': 'Nothing', | |
'0': 'Arcane', | |
'1': 'Flame', | |
'2': 'Stone', | |
'3': 'Storm', | |
'4': 'Frost', | |
'5': 'Underdark', | |
'6': 'OverLight', | |
'7': 'Nature', | |
'8': 'Seas', | |
'9': 'Cogs', | |
'10': 'Seasons', | |
'11': 'Illusion', | |
'12': 'Blood', | |
'13': 'Druidism', | |
'14': 'Cosmos', | |
'15': 'Sands' | |
}; | |
const replays = Object.entries(JSON.parse(await fs.promises.readFile('./replays.json', 'utf-8'))).toSorted(([, replay1], [, replay2]) => new Date(replay1.timestamp) - new Date(replay2.timestamp)); | |
let i = 0; | |
for (const [id, {timestamp, content}] of replays/*.slice(foundReplayIndex)*/) { | |
const matches = [...content.replaceAll('**', '').matchAll(/(?:\[#\d+] \[.*])?\[(.+?)] (-?\d+) \{([+\-\d]+)}/g)]; | |
const records = matches.map(([_, name, rating, ratingChange]) => ({ | |
name, preMatchRating: +rating, ratingChange: +ratingChange, postMatchRating: +rating + +ratingChange | |
})); | |
const buffer = await fs.promises.readFile(`./replays/${id}`); | |
const replay = getReplay(createKbuffer(buffer)); | |
if (replay === null) { | |
continue; | |
} | |
const clientVersion = replay.version; | |
const gameId = id; | |
const players = replay.gameFacts.players.map((player) => player.account); | |
const amountOfPlayers = players.length; | |
const description = replay.gameFacts.gameSettings.description; | |
const gameType = replay.gameFacts.gameSettings.gameType; | |
const wasDraw = replay.winners.length === 0; | |
if (replay.gameFacts.customArmageddon != undefined) { | |
console.log(replay.gameFacts.customArmageddon); | |
} | |
if (i % 1000 === 0) { | |
console.log(i + '/' + replays.length); | |
} | |
const {seed, gameSettings: {inviteMode, spectatorMode, timeMode, teamMode, multiTeamMode, playerMode, playersPerTeam, mapMode, elementalLevel, startHealth, armageddonTurn, customTime, customPlayerCount, mapWidth, mapHeight, numberOfPlayersPerTeam}} = replay.gameFacts; | |
await pool.query( | |
'INSERT INTO game (id, version, amount_of_players, description, game_type, timestamp, was_draw, seed, invite_mode, spectator_mode, time_mode, team_mode, multi_team_mode, player_mode, players_per_team, map, elemental_level, start_health, armageddon_turn, custom_time, custom_player_count, map_width, map_height, number_of_players_per_team) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)', | |
[gameId, clientVersion, amountOfPlayers, description, gameType, timestamp, wasDraw, seed, inviteMode, spectatorMode, timeMode, teamMode, multiTeamMode, playerMode, playersPerTeam, mapMode, elementalLevel, startHealth, armageddonTurn, customTime, customPlayerCount, mapWidth, mapHeight, numberOfPlayersPerTeam] | |
); | |
for (const enabledStyle of replay.gameFacts.gameSettings.gameStyles) { | |
await pool.query( | |
'INSERT INTO game_style (game_id, enabled_style) VALUES ($1, $2)', | |
[gameId, enabledStyle] | |
); | |
} | |
for (let {account: {name, oldName, rating1, rating2, rating3, discord}, accountSettings: {fullBook, spells}} of replay.gameFacts.players) { | |
if (name === 'Lemon Caster' || name === 'xLemonKush') { | |
discord = '991846146626555994'; | |
// use 991846146626555994 | |
} else if (name === 'xLemonTree') { | |
discord = '339812968185200660'; | |
} | |
const userWon = replay.winners.includes(name); | |
const indexOfPlayerInPlayersArray = replay.gameFacts.players.findIndex((player) => player.account.name === name); | |
const rateGain = records[indexOfPlayerInPlayersArray].ratingChange; | |
const dbRecordForDiscordId = (await pool.query('SELECT * FROM "user" WHERE id = $1', [discord]))?.rows?.[0]; | |
const discordIdAlreadyInDatabase = dbRecordForDiscordId !== undefined; | |
try { | |
if (!discordIdAlreadyInDatabase) { | |
const dbRecordForNameAndPreviousName = (await pool.query('SELECT * FROM "user" WHERE name = $1 AND previous_name = $2', [name, oldName]))?.rows?.[0]; | |
const nameAndPreviousNameAlreadyInDatabase = dbRecordForNameAndPreviousName !== undefined; | |
if (nameAndPreviousNameAlreadyInDatabase) { | |
await pool.query('UPDATE "user" SET id = $1, lts_rating = $2, hts_rating = $3, pmo_rating = $4 WHERE name = $5', [discord, rating1, rating2, rating3, name]); | |
} else { | |
await pool.query( | |
'INSERT INTO "user" (id, name, previous_name, lts_rating, hts_rating, pmo_rating) VALUES ($1, $2, $3, $4, $5, $6)', | |
[discord, name, oldName, rating1, rating2, rating3] | |
) | |
} | |
} else { | |
await pool.query('UPDATE "user" SET name = $1, previous_name = $2, lts_rating = $3, hts_rating = $4, pmo_rating = $5 WHERE id = $6', [name, oldName, rating1, rating2, rating3, discord]); | |
} | |
} catch (e) { | |
console.error(e); | |
console.log(gameId); | |
console.log(timestamp); | |
console.log(name); | |
console.log(oldName); | |
console.log(discord); | |
process.exit(1); | |
} | |
const postGameRate = {'low': rating1, 'high': rating2, 'party': rating3}[replay.gameFacts.gameSettings.gameType]; | |
const wentFirst = replay.firstPlayerToDoAMove === name; | |
await pool.query( | |
'INSERT INTO user_game (user_id, game_id, won, rate_gain, post_game_rate, went_first, book) VALUES ($1, $2, $3, $4, $5, $6, $7)', | |
[discord, gameId, userWon, rateGain, postGameRate, wentFirst, bookEnum[fullBook - 1]] | |
); | |
for (const spellIndex of spells) { | |
await pool.query( | |
'INSERT INTO user_game_spell_in_book (user_id, game_id, spell) VALUES ($1, $2, $3)', | |
[discord, gameId, spellsEnum[spellIndex]] | |
); | |
} | |
} | |
for (const {player, spell, frame} of replay.timeline) { | |
await pool.query( | |
'INSERT INTO user_game_spell_fired (user_id, game_id, spell, frame) VALUES ($1, $2, $3, $4)', | |
[player, gameId, spell, frame] | |
); | |
} | |
i += 1; | |
} | |
function getReplay(fileKbuffer) { | |
const gameType = ['low', 'high', 'party']; | |
return deserializeReplay(); | |
function deserializeReplay() { | |
const replay = {}; | |
replay.version = fileKbuffer.readString(); | |
if (replay.version === 'v7.0') { | |
return null; | |
} | |
fileKbuffer.readBool(); | |
replay.server = fileKbuffer.readString(); | |
replay.gameFacts = deserializeGameFacts(replay.version); | |
replay.num = fileKbuffer.readInt32(); | |
replay.timeline = []; | |
replay.timelineList = []; | |
replay.firstPlayerToDoAMove = null; | |
for (let i = 0; i < replay.num; i++) { | |
const timelineBytes = fileKbuffer.readBytes(); | |
const kbuffer = createKbuffer(timelineBytes); | |
//replay.timeline.push(kbuffer); | |
const firstByte = kbuffer.readByte(); | |
const playerIndex = timelineBytes.length > 1 && firstByte >= 175 ? kbuffer.readByte() : null; | |
if (replay.firstPlayerToDoAMove === null && playerIndex !== null) { | |
replay.firstPlayerToDoAMove = replay.gameFacts.players[playerIndex].account.name; | |
} | |
if (firstByte === 13) { | |
const winners = []; | |
const losers = []; | |
for (let j = 0; j < replay.gameFacts.players.length; j++) { | |
const isAlive = kbuffer.readBool(); | |
if (isAlive) { | |
winners.push(replay.gameFacts.players[j].account.name); | |
} else { | |
losers.push(replay.gameFacts.players[j].account.name); | |
} | |
} | |
const msg = kbuffer.readString(); | |
for (let j = 0; j < replay.gameFacts.players.length; j++) { | |
const wandsGained = kbuffer.readInt32(); | |
} | |
replay.winners = winners; | |
replay.losers = losers; | |
} else if (firstByte === 198) { | |
const turn = kbuffer.readInt32(); | |
replay.timelineList.push({ | |
turn: turn + 2, | |
player: replay.gameFacts.players[playerIndex].account.name, | |
frame: i | |
}); | |
} else if (firstByte === 220) { | |
const gameId = kbuffer.readInt32(); | |
const nextMoveId = kbuffer.readInt32(); | |
const selectedCreatureIndex = kbuffer.readByte(); | |
const selectedCreaturePlayerOffset = kbuffer.readByte(); | |
const selectedSpell = kbuffer.readInt32(); | |
/*const spellBonusDmg = kbuffer.readInt32(); | |
const spellIsPresent = kbuffer.readBool(); | |
const spellEndsTurn = kbuffer.readBool(); | |
const selectedSpellIndex = kbuffer.readByte(); | |
const rotZ = kbuffer.readFixed(); | |
const zCreatureSelectedTransformScaleIsLargerThan0 = kbuffer.readBool(); | |
const spellTarget = kbuffer.readMyLocation(); | |
const meterFillAmount = kbuffer.readFixed(); | |
const isExtendedShot = kbuffer.readBool(); | |
const keyDown = kbuffer.readInt32(); | |
const zCreatureSelectedPosition = kbuffer.readMyLocation(); | |
const secTarget = kbuffer.readMyLocation();*/ | |
replay.timeline.push({ | |
description: 'spell', | |
player: replay.gameFacts.players[playerIndex].account.discord, | |
spell: spellsEnum[selectedSpell], | |
frame: i | |
}); | |
} | |
} | |
return replay; | |
} | |
function deserializeGameFacts(clientVersion) { | |
const gameFacts = {}; | |
gameFacts.gameId = fileKbuffer.readInt32(); | |
gameFacts.amountOfPlayers = fileKbuffer.readInt32(); | |
gameFacts.realMap = fileKbuffer.readInt32(); | |
gameFacts.port = fileKbuffer.readInt32(); | |
gameFacts.seed = fileKbuffer.readInt32(); | |
gameFacts.players = []; | |
for (let i = 0; i < gameFacts.amountOfPlayers; i++) { | |
gameFacts.players.push({ | |
account: deserializeAccount(), accountSettings: deserializeAccountSettings() | |
}); | |
} | |
gameFacts.gameSettings = deserializeGameSettings(clientVersion); | |
gameFacts.status = fileKbuffer.readByte(); | |
if (gameFacts.status !== 0) { | |
gameFacts.gameSettings.mapMode = mapEnum[gameFacts.realMap]; | |
} | |
gameFacts.gameSettings.numberOfPlayersPerTeam = getActualNumberOfPlayersPerTeam(gameFacts); | |
gameFacts.serverStartTime = fileKbuffer.readInt32(); | |
if (clientVersion !== 'v7.1') { | |
gameFacts.customQueue = fileKbuffer.readInt32(); | |
} | |
return gameFacts; | |
} | |
function getActualNumberOfPlayersPerTeam(gameFacts) { | |
switch (gameFacts.gameSettings.playersPerTeam) { | |
case 'Eight': | |
return 8; | |
case 'Two': | |
return 2; | |
case 'Three': | |
return 3; | |
case 'Four': | |
return 4; | |
case 'Five': | |
return 5; | |
case 'Six': | |
return 6; | |
case 'Seven': | |
return 7; | |
default: | |
if (!gameFacts.gameSettings.multiTeamMode || gameFacts.status === 0 || gameFacts.players.length <= 2) { | |
return Math.floor(Math.max(2, (1 + gameFacts.players.length) / 2)); | |
} else { | |
const firstPlayerName = gameFacts.players[0].account.name; | |
return gameFacts.players.filter(({account: {name}}) => name === firstPlayerName).length; | |
} | |
} | |
} | |
function hasStyle(s, t) { | |
return ((s & t) >>> 0) > 0; | |
} | |
function deserializeGameSettings(clientVersion) { | |
const gameSettings = {}; | |
gameSettings.description = fileKbuffer.readString(); | |
const gameModes = fileKbuffer.readInt32(); | |
gameSettings.inviteMode = inviteEnum[gameModes & 31]; | |
gameSettings.spectatorMode = (gameModes & 64) > 0; | |
gameSettings.ratedMode = (gameModes & 128) > 0; | |
gameSettings.tournamentMode = (gameModes & 32768) > 0; | |
gameSettings.timeMode = timeEnum[gameModes & 1343264]; | |
gameSettings.teamMode = (gameModes & 16777216) > 0; | |
gameSettings.multiTeamMode = (gameModes & 524288) > 0; | |
gameSettings.playerMode = playerEnum[gameModes & 2113929216]; | |
const gameModes2 = fileKbuffer.readInt32(); | |
gameSettings.allowArcanePowers = (gameModes2 & 131072) !== 0; | |
gameSettings.playersPerTeam = playersPerTeamEnum[gameModes2 & 2117074944]; | |
gameSettings.style = gameModes2; //gameStyleEnum[gameModes2 & 258459]; | |
gameSettings.gameStyles = Object.entries(gameStyleEnum).filter(([bitStuff]) => hasStyle(gameSettings.style, +bitStuff)).map(([_, name]) => name); | |
gameSettings.isNonStandard = (gameModes2 & 254362) !== 0; | |
const gameModes3 = fileKbuffer.readInt32(); | |
gameSettings.armageddon = mapEnum[gameModes3 & 1342146160]; | |
const gameModes4 = fileKbuffer.readInt32(); | |
gameSettings.mapMode = mapEnum[gameModes4 & 1342146160]; | |
gameSettings.elementalLevel = fileKbuffer.readByte(); | |
gameSettings.startHealth = fileKbuffer.readUInt16(); | |
gameSettings.armageddonTurn = fileKbuffer.readByte(); | |
gameSettings.countdownTime = fileKbuffer.readInt16(); | |
gameSettings.countdownDelay = fileKbuffer.readByte(); | |
gameSettings.customTime = fileKbuffer.readUInt16(); | |
gameSettings.customPlayerCount = fileKbuffer.readByte(); | |
gameSettings.gameType = gameType[Math.min(2, Math.max(0, fileKbuffer.readInt32()))]; | |
if (clientVersion !== 'v7.1') { | |
gameSettings.altGeneration = fileKbuffer.readBool(); | |
gameSettings.water = fileKbuffer.readInt32(); | |
} | |
gameSettings.version1 = fileKbuffer.readByte(); | |
if (gameSettings.version1 > 0) { | |
gameSettings.restrictions = deserializeRestrictions(); | |
} else { | |
gameSettings.restrictions = null; | |
} | |
gameSettings.mapSeed = fileKbuffer.readInt32(); | |
gameSettings.fixedMapSeed = fileKbuffer.readBool(); | |
gameSettings.mapWidth = fileKbuffer.readByte(); | |
gameSettings.mapHeight = fileKbuffer.readByte(); | |
gameSettings.customArmageddonCount = fileKbuffer.readInt32(); | |
if (gameSettings.customArmageddonCount > 0 && gameSettings.customArmageddonCount <= 5) { | |
gameSettings.customArmageddon = []; | |
for (let i = 0; i < gameSettings.customArmageddonCount; i++) { | |
gameSettings.customArmageddon.push(fileKbuffer.readInt32()); | |
} | |
} else { | |
gameSettings.customArmageddon = null; | |
} | |
if (clientVersion !== 'v7.1') { | |
gameSettings.num2 = fileKbuffer.readInt32(); | |
if (gameSettings.num2 > 0 && gameSettings.num2 <= 250) { | |
gameSettings.autoInclude = []; | |
for (let i = 0; i < gameSettings.num2; i++) { | |
gameSettings.autoInclude.push(fileKbuffer.readInt32()); | |
} | |
} else { | |
gameSettings.autoInclude = null; | |
} | |
} | |
return gameSettings; | |
} | |
function deserializeRestrictions() { | |
const restrictions = {}; | |
restrictions.availableSpells = []; | |
for (let i = 0; i < 8; i++) { | |
restrictions.availableSpells.push(fileKbuffer.readInt32()); | |
} | |
restrictions.elementals = fileKbuffer.readInt32(); | |
return restrictions; | |
} | |
function deserializeAccount() { | |
const account = {}; | |
account.name = fileKbuffer.readString(); | |
account.rating1 = fileKbuffer.readInt16(); | |
account.rating2 = fileKbuffer.readInt16(); | |
account.rating3 = fileKbuffer.readInt16(); | |
account.accountType = fileKbuffer.readInt32(); | |
account.experience = fileKbuffer.readByte(); | |
account.discord = fileKbuffer.readUInt64(); | |
account.displayedIcon = fileKbuffer.readInt32(); | |
account.displayClanPrefix = fileKbuffer.readByte(); | |
account.clan = fileKbuffer.readString(); | |
account.clanRole = fileKbuffer.readByte(); | |
account.location = fileKbuffer.readByte(); | |
account.prestige = fileKbuffer.readByte(); | |
account.oldName = fileKbuffer.readString(); | |
account.server = fileKbuffer.readInt32(); | |
account.activeItems = deserializeActiveItems(); | |
return account; | |
} | |
function deserializeActiveItems() { | |
const items = []; | |
const num1 = fileKbuffer.readByte(); | |
const num2 = fileKbuffer.readInt32(); | |
for (let i = 0; i < num2; i++) { | |
items.push({ | |
which: fileKbuffer.readInt32(), index: fileKbuffer.readInt32() | |
}); | |
} | |
return items; | |
} | |
function deserializeAccountSettings() { | |
const accountSettings = {}; | |
accountSettings.accountType = fileKbuffer.readByte(); | |
accountSettings.indexHead = fileKbuffer.readByte(); | |
accountSettings.indexBeard = fileKbuffer.readByte(); | |
if (accountSettings.accountType > 3) { | |
accountSettings.indexBeard2 = fileKbuffer.readByte(); | |
accountSettings.indexBeard3 = fileKbuffer.readByte(); | |
} else { | |
accountSettings.indexBeard2 = 0; | |
accountSettings.indexBeard3 = 0; | |
} | |
accountSettings.indexHat = fileKbuffer.readByte(); | |
accountSettings.indexBody = fileKbuffer.readByte(); | |
accountSettings.indexLeftHand = fileKbuffer.readByte(); | |
accountSettings.indexRightHand = fileKbuffer.readByte(); | |
accountSettings.spells = []; | |
for (let i = 0; i < 16; i++) { | |
accountSettings.spells[i] = fileKbuffer.readByte(); | |
} | |
if (accountSettings.accountType <= 1) { | |
accountSettings.num2 = fileKbuffer.readByte(); | |
accountSettings.num3 = fileKbuffer.readByte(); | |
accountSettings.num4 = fileKbuffer.readByte(); | |
accountSettings.num5 = fileKbuffer.readByte(); | |
if (accountSettings.accountType > 0) { | |
accountSettings.fullBook = fileKbuffer.readByte(); | |
} | |
} else { | |
accountSettings.coloring = deserializeColoring(); | |
accountSettings.fullBook = fileKbuffer.readByte(); | |
accountSettings.extraInfo = accountSettings.accountType <= 2 ? 0 : fileKbuffer.readByte(); | |
accountSettings.num2 = fileKbuffer.readByte(); | |
if (accountSettings.num2 !== 0) { | |
accountSettings.custom = accountSettings.num2; | |
accountSettings.indexMouth = fileKbuffer.readByte(); | |
accountSettings.indexLeftFoot = fileKbuffer.readByte(); | |
accountSettings.indexRightFoot = fileKbuffer.readByte(); | |
accountSettings.num3 = fileKbuffer.readByte(); | |
accountSettings.customPieces = []; | |
for (let i = 0; i < accountSettings.num3; i++) { | |
if (i < 11) { | |
accountSettings.customPieces[i] = fileKbuffer.readString(); | |
} | |
} | |
} | |
} | |
return accountSettings; | |
} | |
function deserializeColoring() { | |
const result = []; | |
const num = fileKbuffer.readInt32(); | |
if (num > 11) { | |
return; | |
} | |
for (let i = 0; i < num; i++) { | |
const red = fileKbuffer.readColor32NoAlpha(); | |
const green = fileKbuffer.readColor32NoAlpha(); | |
const blue = fileKbuffer.readColor32NoAlpha(); | |
const gray = fileKbuffer.readColor32NoAlpha(); | |
result.push({red, green, blue, gray}); | |
} | |
return result; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Kbuffer { | |
constructor(buffer) { | |
this.buffer = buffer; | |
this.offset = 0; | |
} | |
readBytes() { | |
const readLength = this.readInt32(); | |
const buffer = this.buffer.subarray(this.offset, this.offset + readLength); | |
this.offset += readLength; | |
return buffer; | |
} | |
readUInt16() { | |
const uint16 = this.buffer.readUInt16LE(this.offset); | |
this.offset += 2; | |
return uint16; | |
} | |
readColor32NoAlpha() { | |
return { | |
a: 255, r: this.readByte(), g: this.readByte(), b: this.readByte() | |
}; | |
} | |
readUInt64() { | |
const uint64 = this.buffer.readBigUint64LE(this.offset); | |
this.offset += 8; | |
return uint64; | |
} | |
readInt64() { | |
const int64 = this.buffer.readBigInt64LE(this.offset); | |
this.offset += 8; | |
return int64; | |
} | |
readMyLocation() { | |
return {x: this.readInt64(), y: this.readInt64()}; | |
} | |
readByte() { | |
const byte = this.buffer.readUInt8(this.offset); | |
this.offset += 1; | |
return byte; | |
} | |
readInt16() { | |
const int16 = this.buffer.readInt16LE(this.offset); | |
this.offset += 2; | |
return int16; | |
} | |
readInt32() { | |
const int32 = this.buffer.readInt32LE(this.offset); | |
this.offset += 4; | |
return int32; | |
} | |
readBool() { | |
const bool = this.buffer.readUInt8(this.offset); | |
this.offset += 1; | |
return bool !== 0; | |
} | |
readString() { | |
const length = this.buffer.readUInt8(this.offset); | |
this.offset += 1; | |
const str = this.buffer.subarray(this.offset, this.offset + length).toString(); | |
this.offset += length; | |
return str; | |
} | |
readFixed() { | |
const low = this.buffer.readUint32LE(this.offset); | |
this.offset += 4; | |
const high = this.buffer.readUint32LE(this.offset); | |
this.offset += 4; | |
return high * 2 ** 32 + low; | |
} | |
} | |
const kBufferHandler = { | |
get(target, prop) { | |
if (typeof prop === 'symbol' || isNaN(prop)) { | |
return target[prop]; | |
} | |
const index = Number(prop); | |
return target.buffer[index]; | |
} | |
}; | |
export default function createKbuffer(buffer) { | |
const wrapper = new Kbuffer(buffer); | |
return new Proxy(wrapper, kBufferHandler); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment