Go crazy with colored hexdump

This commit is contained in:
ChillerDragon 2022-11-16 15:45:04 +01:00
parent 2d4dc6dc65
commit c119d393fc
3 changed files with 73 additions and 11 deletions

View file

@ -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

View file

@ -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

View file

@ -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