From 34ebe75d54a5b3c54e31633470f8aa8bcdf888c7 Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Thu, 17 Nov 2022 16:11:34 +0100 Subject: [PATCH] Move snapshot to new file (still not cleaned up) --- lib/game_client.rb | 237 +-------------------------------------------- lib/snapshot.rb | 171 ++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 235 deletions(-) create mode 100644 lib/snapshot.rb diff --git a/lib/game_client.rb b/lib/game_client.rb index 250727c..e0c4895 100644 --- a/lib/game_client.rb +++ b/lib/game_client.rb @@ -10,6 +10,7 @@ require_relative 'messages/maplist_entry_add' require_relative 'messages/maplist_entry_rem' require_relative 'packer' require_relative 'context' +require_relative 'snapshot' class GameClient attr_accessor :players, :pred_game_tick, :ack_game_tick @@ -153,247 +154,13 @@ class GameClient end def on_snapshot(chunk) - u = Unpacker.new(chunk.data) - msg_id = u.get_int - msg_id >>= 1 - - num_parts = 1 - part = 0 - game_tick = u.get_int - delta_tick = u.get_int - part_size = 0 - crc = 0 - # complete_size = 0 - # data = nil - - # TODO: state check - - if msg_id == NETMSG_SNAP - num_parts = u.get_int - part = u.get_int - end - - unless msg_id == NETMSG_SNAPEMPTY - crc = u.get_int - part_size = u.get_int - end - - snap_name = 'SNAP_INVALID' - case msg_id - when NETMSG_SNAP then snap_name = 'NETMSG_SNAP' - when NETMSG_SNAPSINGLE then snap_name = 'NETMSG_SNAPSINGLE' - when NETMSG_SNAPEMPTY then snap_name = 'NETMSG_SNAPEMPTY' - end - - return unless msg_id == NETMSG_SNAPSINGLE - - puts ">>> snap #{snap_name} (#{msg_id})" - puts " id=#{msg_id} game_tick=#{game_tick} delta_tick=#{delta_tick}" - puts " num_parts=#{num_parts} part=#{part} crc=#{crc} part_size=#{part_size}" - puts "\n header:" - - header = [] - notes = [] - u.parsed.each_with_index do |parsed, index| - color = (index % 2).zero? ? :green : :pink - txt = "#{parsed[:type]} #{parsed[:value]}" - txt += " >> 1 = #{parsed[:value] >> 1}" if header.empty? - notes.push([color, parsed[:pos], parsed[:len], txt]) - header += parsed[:raw] - end - - hexdump_lines(header.pack('C*'), 1, notes, legend: :inline).each do |hex| - puts " #{hex}" - end - - puts "\n payload:" - # [:green, 0, 4, 'who dis?'] - notes = [] - # data.groups_of(4).each_with_index do |item, index| - # # reverse for little endian - # type = item[0...2].reverse.map { |b| b.to_s(2).rjust(8, '0') }.join.to_i(2) - # notes.push([:green, index * 4, 2, "type=#{type}"]) - # next unless item.length == 4 - - # # reverse for little endian - # id = item[2...4].reverse.map { |b| b.to_s(2).rjust(8, '0') }.join.to_i(2) - # notes.push([:yellow, index * 4 + 2, 2, "id=#{id}"]) - # end - - @sizes = [ - 0, - 10, - 6, - 5, - 3, - 3, - 3, - 2, - 4, - 15, - 22, - 3, - 4, - 58, - 5, - 32, - 2, - 2, - 2, - 2, - 3, - 3, - 5 - ] - - @snap_items = [ - { name: 'placeholder', size: 0 }, - { name: 'obj_player_input', size: 10 }, - { name: 'obj_projectile', size: 6 }, - { name: 'obj_laser', size: 5 }, - { name: 'obj_pickup', size: 3 }, - { name: 'obj_flag', size: 3 }, - { name: 'obj_game_data', size: 3, fields: [ - { type: 'int', name: 'start_tick' }, - { type: 'int', name: 'flags' }, - { type: 'int', name: 'end_tick' } - ] }, - { name: 'obj_game_data_team', size: 2 }, - { name: 'obj_game_data_flag', size: 4 }, - { name: 'obj_character_core', size: 15 }, - { name: 'obj_character', size: 22 }, - { name: 'obj_player_info', size: 3 }, - { name: 'obj_spectator_info', size: 4 }, - { name: 'obj_client_info', size: 58 }, - { name: 'obj_game_info', size: 5 }, - { name: 'obj_tune_params', size: 32 }, - { name: 'event_common', size: 2 }, - { name: 'event_explosion', size: 2 }, - { name: 'event_spawn', size: 2 }, - { name: 'event_hammerhit', size: 2 }, - { name: 'event_death', size: 3 }, - { name: 'event_sound_world', size: 3 }, - { name: 'event_damage', size: 5 } - ] - - data = u.get_raw - - # tw decompresses all bytes at once - # and pads it with zeros to get the 4 byte aligned ints - # we just grab one int at a time cuz yolo - u = Unpacker.new(data) - num_removed_items = u.get_int - p = u.parsed.last - notes.push([:red, p[:pos], p[:len], "removed_items=#{num_removed_items}"]) - - num_item_deltas = u.get_int - p = u.parsed.last - notes.push([:blue, p[:pos], p[:len], "num_item_deltas=#{num_item_deltas}"]) - - zero = u.get_int - p = u.parsed.last - notes.push([:cyan, p[:pos], p[:len], "_zero=#{zero}"]) - - (0...num_removed_items).each do |i| - deleted = u.get_int - notes.push([:red, p[:pos], p[:len], "del[#{i}]=#{deleted}"]) - end - - item_delta = u.get_int - while item_delta - # item_bits = item_delta.to_s(2).rjust(32, '0') - item_type = item_delta - # item_id_bits = item_bits[0...16] - # item_type_bits = item_bits[16..] - # item_id = item_id_bits.to_i(2) - # item_type = item_type_bits.to_i(2) - item_meta = @snap_items[item_type] - item_name = item_meta[:name] - # { name: 'obj_game_data', size: 3, fields: [ - # { type: 'int', name: 'start_tick' }, - - p = u.parsed.last - notes.push([:green, p[:pos], p[:len], "type = #{item_type} #{item_name}"]) - - item_id = u.get_int - p = u.parsed.last - notes.push([:cyan, p[:pos], p[:len], "id=#{item_id}"]) - - size = item_meta[:size] - (0...size).each do |i| - val = u.get_int - p = u.parsed.last - color = (i % 2).zero? ? :yellow : :pink - fields = item_meta[:fields] - desc = '' - desc = fields[i][:name] unless fields.nil? || fields[i].nil? - notes.push([color, p[:pos], p[:len], "data[#{i}]=#{val} #{desc}"]) - end - item_delta = u.get_int - end - - # skip = 0 - # ((3 * 4)...data.size).each do |i| - # skip -= 1 - # unless skip.negative? - # # puts "skipped i=#{i} hex=#{str_hex([data[i]].pack('C*'))} skips_left=#{skip}" - # next - # end - - # # reverse for little endian - # id = data[i...(i + 2)].reverse.map { |b| b.to_s(2).rjust(8, '0') }.join.to_i(2) - - # if data[i + 4].nil? && i > 2 - # puts "Error: unexpected end of data at i=#{i + 4} data_size=#{data.size}" - # next - # end - - # type = data[(i + 2)...(i + 4)].reverse.map { |b| b.to_s(2).rjust(8, '0') }.join.to_i(2) - # size = @sizes[type] - # # p "id=#{id} type=#{type}" - - # if size.nil? && i > 2 - # puts "Error: could not get size for type=#{type} -> skip byte" - # next - # end - - # size *= 4 - - # meta = @snap_items[type] - - # notes.push([:green, i, 2, "id=#{id}"]) - # notes.push([:pink, i + 2, 2, "type=#{type} (#{meta[:name]} size: #{size})"]) - - # item_payload = data[(i + 4)..] - # u = Unpacker.new(item_payload) - # (0...(size / 4)).each do |d| - # # val = u.get_int - # val='EMPTY' - # field_name = '' - # field_name += meta[:fields][d][:name] unless meta[:fields].nil? || meta[:fields][d].nil? - # notes.push([:yellow, i + 4 + (d * 4), 4, "data[#{d}]=#{val} #{field_name}"]) - # end - # skip += 3 + size + 1 - # # puts "skip=#{skip}" - - # # next - # # next unless item.length == 4 - - # # # reverse for little endian - # # id = item[2...4].reverse.map { |b| b.to_s(2).rjust(8, '0') }.join.to_i(2) - # # notes.push([:yellow, (index * 4) + 2, 2, "id=#{id}"]) - # end - - hexdump_lines(data.pack('C*'), 1, notes, legend: :inline).each do |hex| - puts " #{hex}" - end + game_tick = snap_single(chunk) # ack every snapshot no matter how broken @ack_game_tick = game_tick return unless (@pred_game_tick - @ack_game_tick).abs > 10 @pred_game_tick = @ack_game_tick + 1 - # exit end def on_emoticon(chunk); end diff --git a/lib/snapshot.rb b/lib/snapshot.rb new file mode 100644 index 0000000..abebdef --- /dev/null +++ b/lib/snapshot.rb @@ -0,0 +1,171 @@ +# frozen_string_literal: true + +require_relative 'packer' + +def snap_single(chunk) + u = Unpacker.new(chunk.data) + msg_id = u.get_int + msg_id >>= 1 + + num_parts = 1 + part = 0 + game_tick = u.get_int + delta_tick = u.get_int + part_size = 0 + crc = 0 + # complete_size = 0 + # data = nil + + # TODO: state check + + if msg_id == NETMSG_SNAP + num_parts = u.get_int + part = u.get_int + end + + unless msg_id == NETMSG_SNAPEMPTY + crc = u.get_int + part_size = u.get_int + end + + snap_name = 'SNAP_INVALID' + case msg_id + when NETMSG_SNAP then snap_name = 'NETMSG_SNAP' + when NETMSG_SNAPSINGLE then snap_name = 'NETMSG_SNAPSINGLE' + when NETMSG_SNAPEMPTY then snap_name = 'NETMSG_SNAPEMPTY' + end + + return unless msg_id == NETMSG_SNAPSINGLE + + puts ">>> snap #{snap_name} (#{msg_id})" + puts " id=#{msg_id} game_tick=#{game_tick} delta_tick=#{delta_tick}" + puts " num_parts=#{num_parts} part=#{part} crc=#{crc} part_size=#{part_size}" + puts "\n header:" + + header = [] + notes = [] + u.parsed.each_with_index do |parsed, index| + color = (index % 2).zero? ? :green : :pink + txt = "#{parsed[:type]} #{parsed[:value]}" + txt += " >> 1 = #{parsed[:value] >> 1}" if header.empty? + notes.push([color, parsed[:pos], parsed[:len], txt]) + header += parsed[:raw] + end + + hexdump_lines(header.pack('C*'), 1, notes, legend: :inline).each do |hex| + puts " #{hex}" + end + + puts "\n payload:" + notes = [] + + @sizes = [ + 0, + 10, + 6, + 5, + 3, + 3, + 3, + 2, + 4, + 15, + 22, + 3, + 4, + 58, + 5, + 32, + 2, + 2, + 2, + 2, + 3, + 3, + 5 + ] + + @snap_items = [ + { name: 'placeholder', size: 0 }, + { name: 'obj_player_input', size: 10 }, + { name: 'obj_projectile', size: 6 }, + { name: 'obj_laser', size: 5 }, + { name: 'obj_pickup', size: 3 }, + { name: 'obj_flag', size: 3 }, + { name: 'obj_game_data', size: 3, fields: [ + { type: 'int', name: 'start_tick' }, + { type: 'int', name: 'flags' }, + { type: 'int', name: 'end_tick' } + ] }, + { name: 'obj_game_data_team', size: 2 }, + { name: 'obj_game_data_flag', size: 4 }, + { name: 'obj_character_core', size: 15 }, + { name: 'obj_character', size: 22 }, + { name: 'obj_player_info', size: 3 }, + { name: 'obj_spectator_info', size: 4 }, + { name: 'obj_client_info', size: 58 }, + { name: 'obj_game_info', size: 5 }, + { name: 'obj_tune_params', size: 32 }, + { name: 'event_common', size: 2 }, + { name: 'event_explosion', size: 2 }, + { name: 'event_spawn', size: 2 }, + { name: 'event_hammerhit', size: 2 }, + { name: 'event_death', size: 3 }, + { name: 'event_sound_world', size: 3 }, + { name: 'event_damage', size: 5 } + ] + + data = u.get_raw + + # tw decompresses all bytes at once + # and pads it with zeros to get the 4 byte aligned ints + # we just grab one int at a time cuz yolo + u = Unpacker.new(data) + num_removed_items = u.get_int + p = u.parsed.last + notes.push([:red, p[:pos], p[:len], "removed_items=#{num_removed_items}"]) + + num_item_deltas = u.get_int + p = u.parsed.last + notes.push([:blue, p[:pos], p[:len], "num_item_deltas=#{num_item_deltas}"]) + + zero = u.get_int + p = u.parsed.last + notes.push([:cyan, p[:pos], p[:len], "_zero=#{zero}"]) + + (0...num_removed_items).each do |i| + deleted = u.get_int + notes.push([:red, p[:pos], p[:len], "del[#{i}]=#{deleted}"]) + end + + item_delta = u.get_int + while item_delta + item_type = item_delta + item_meta = @snap_items[item_type] + item_name = item_meta[:name] + + p = u.parsed.last + notes.push([:green, p[:pos], p[:len], "type = #{item_type} #{item_name}"]) + + item_id = u.get_int + p = u.parsed.last + notes.push([:cyan, p[:pos], p[:len], "id=#{item_id}"]) + + size = item_meta[:size] + (0...size).each do |i| + val = u.get_int + p = u.parsed.last + color = (i % 2).zero? ? :yellow : :pink + fields = item_meta[:fields] + desc = '' + desc = fields[i][:name] unless fields.nil? || fields[i].nil? + notes.push([color, p[:pos], p[:len], "data[#{i}]=#{val} #{desc}"]) + end + item_delta = u.get_int + end + hexdump_lines(data.pack('C*'), 1, notes, legend: :inline).each do |hex| + puts " #{hex}" + end + + game_tick +end