diff --git a/lib/bytes.rb b/lib/bytes.rb index e34e2ca..3df8263 100644 --- a/lib/bytes.rb +++ b/lib/bytes.rb @@ -16,7 +16,15 @@ def data_to_ascii(data) ascii end -def hexdump_lines(data, width = 2, notes = [], opts = {}) +COL_LEN = 9 + +# TODO: make this a gem?! +# +# opts +# legend: :long +# legend: :short +# legend: :inline +def hexdump_lines(data, width = 2, notes = [], opts = { legend: :long }) byte_groups = data.unpack1('H*').scan(/../).groups_of(4) lines = [] hex = '' @@ -31,11 +39,12 @@ def hexdump_lines(data, width = 2, notes = [], opts = {}) legend.push([color, info.last.send(color)]) end unless legend.empty? - if opts[:long_legend] + case opts[:legend] + when :long legend.each do |leg| lines.push("#{leg.first}: #{leg.last}".send(leg.first)) end - else + when :short lines.push(legend.map(&:last).join(' ')) end end @@ -43,6 +52,8 @@ def hexdump_lines(data, width = 2, notes = [], opts = {}) hex += ' ' unless hex.empty? ascii += data_to_ascii(str_bytes(byte_group.join).pack('C*')) w += 1 + note = '' + colors = 0 notes.each do |info| color = info.first # p color @@ -59,6 +70,8 @@ def hexdump_lines(data, width = 2, notes = [], opts = {}) next end + note += " #{info[3]}".send(color) if opts[:legend] == :inline + from -= byte to -= byte from = 0 if from.negative? @@ -69,6 +82,7 @@ def hexdump_lines(data, width = 2, notes = [], opts = {}) next if byte_group[i].nil? byte_group[i] = byte_group[i].send(color) + colors += 1 end end byte += 4 @@ -76,11 +90,13 @@ def hexdump_lines(data, width = 2, notes = [], opts = {}) next unless w >= width w = 0 - lines.push("#{hex} #{ascii}") + hex_pad = hex.ljust((width * 4 * 3) + (colors * COL_LEN), ' ') + ascii_pad = ascii.ljust(width * 4) + lines.push("#{hex_pad} #{ascii_pad}#{note}") hex = '' ascii = '' end - lines.push("#{hex} #{ascii}") unless hex.empty? + lines.push("#{hex_pad} #{ascii_pad}#{note}") unless hex.empty? lines end @@ -112,12 +128,12 @@ def todo_make_this_a_unit_test puts l end - hexdump_lines("\x01\x41\x02\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\xef", 40, notes, long_legend: true).each do |l| + hexdump_lines("\x01\x41\x02\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\xef", 40, notes, legend: long).each do |l| puts l end # should not crash when annotating bytes out of range - hexdump_lines("\x01\x41", 40, notes, long_legend: false).each do |l| + hexdump_lines("\x01\x41", 40, notes, legend: :long).each do |l| puts l end end diff --git a/lib/game_client.rb b/lib/game_client.rb index d8b86eb..c54c53a 100644 --- a/lib/game_client.rb +++ b/lib/game_client.rb @@ -178,13 +178,27 @@ class GameClient part_size = u.get_int end - data = u.get_raw puts "snap id=#{msg_id} game_tick=#{game_tick} delta_tick=#{delta_tick}" puts " num_parts=#{num_parts} part=#{part} crc=#{crc} part_size=#{part_size}" + + header = [] + notes = [] + u.parsed.each_with_index do |parsed, index| + header += parsed[:raw] + color = (index % 2).zero? ? :green : :pink + notes.push([color, parsed[:pos], parsed[:len], "#{parsed[:type]} #{parsed[:value]}"]) + parsed[:value] + end + + hexdump_lines(header.pack('C*'), 1, notes, legend: :inline).each do |hex| + puts hex + end + + data = u.get_raw notes = [ [:green, 0, 4, 'who dis?'] ] - hexdump_lines(data.pack('C*'), 1, notes, long_legend: true).each do |hex| + hexdump_lines(data.pack('C*'), 1, notes, legend: :long).each do |hex| puts hex end diff --git a/lib/packer.rb b/lib/packer.rb index 62d48fd..51ac3cc 100644 --- a/lib/packer.rb +++ b/lib/packer.rb @@ -72,8 +72,14 @@ class Packer end class Unpacker + attr_reader :prev, :data, :parsed + def initialize(data) @data = data + @prev = [] # all the data already unpacked + @parsed = [] + @red_bytes = 0 + # { type: 'int', value: 1, raw: "\x01", len: 1, pos: 0 } if data.instance_of?(String) @data = data.unpack('C*') elsif data.instance_of?(Array) @@ -103,11 +109,19 @@ class Unpacker return nil if @data.nil? str = '' + raw = [] + len = 0 + pos = @red_bytes @data.each_with_index do |byte, index| + @prev.push(byte) + raw.push(raw) + @red_bytes += 1 + len += 1 if byte.zero? @data = index == @data.length - 1 ? nil : @data[(index + 1)..] str = str_sanitize(str) unless (sanitize & SANITIZE).zero? str = str_sanitize_cc(str) unless (sanitize & SANITIZE_CC).zero? + @parsed.push({ type: 'string', value: str, raw:, len:, pos: }) return str end str += byte.chr @@ -130,6 +144,7 @@ class Unpacker sign = first[1] bits = [] + parsed = { type: 'int' } # extended if first[0] == '1' @@ -143,17 +158,34 @@ class Unpacker break if eigth_bits[0] == '0' end bits = bits.reverse + @prev += @data[0...consumed] + parsed[:raw] = @data[0...consumed] + parsed[:len] = consumed + parsed[:pos] = @red_bytes + @red_bytes += consumed @data = @data[consumed..] else # single byte bits = [first[2..]] + @prev.push(@data[0]) + parsed[:raw] = [@data[0]] + parsed[:len] = 1 + parsed[:pos] = @red_bytes + @red_bytes += 1 @data = @data[1..] end num = bits.join.to_i(2) - sign == '1' ? -(num + 1) : num + parsed[:value] = sign == '1' ? -(num + 1) : num + @parsed.push(parsed) + parsed[:value] end def get_raw(size = -1) + len = size == -1 ? @data.size : size + @prev += @data[...len] + pos = @red_bytes + @red_bytes += len + @parsed.push({ type: 'raw', value: @data[...len], raw: @data[...len], len:, pos: }) # TODO: error if size exceeds @data.size - @data.shift(size == -1 ? @data.size : size) + @data.shift(len) end end