kaitai-struct-visualizer 0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,253 @@
1
+ require 'benchmark'
2
+
3
+ require 'kaitai/struct/visualizer/version'
4
+ require 'kaitai/struct/visualizer/node'
5
+ require 'kaitai/struct/visualizer/hex_viewer'
6
+
7
+ module Kaitai::Struct::Visualizer
8
+ class Tree
9
+ def initialize(ui, st)
10
+ @ui = ui
11
+ @st = st
12
+ @root = Node.new(self, st, 0)
13
+ @root.id = '[root]'
14
+ @max_scr_ln = @ui.rows - 3
15
+
16
+ @hv_shift_x = @ui.cols - HexViewer.line_width - 1
17
+
18
+ @cur_io = nil
19
+ @hv = HexViewer.new(ui, nil, @hv_shift_x, self)
20
+ @hv_hidden = false
21
+
22
+ @cur_line = 0
23
+ @cur_shift = 0
24
+ @do_exit = false
25
+ end
26
+
27
+ def run
28
+ c = nil
29
+ loop {
30
+ t = redraw
31
+
32
+ if @cur_node.nil? and not @cur_line.nil?
33
+ # gone beyond the end of the tree
34
+ @cur_line = @root.height - 1
35
+ clamp_cursor
36
+ redraw
37
+ end
38
+
39
+ raise '@cur_line is undetermined' if @cur_line.nil?
40
+ raise '@cur_node is undetermined' if @cur_node.nil?
41
+
42
+ thv = Benchmark.realtime {
43
+ unless @hv_hidden
44
+ hv_update_io
45
+
46
+ unless @cur_node.pos1.nil?
47
+ if (@hv.addr < @cur_node.pos1) or (@hv.addr >= @cur_node.pos2)
48
+ @hv.addr = @cur_node.pos1
49
+ @hv.ensure_visible
50
+ end
51
+ end
52
+
53
+ @hv.redraw
54
+ regs = highlight_regions(4)
55
+ @hv.highlight(regs)
56
+ end
57
+ }
58
+
59
+ @ui.goto(0, @max_scr_ln + 1)
60
+ printf "all: %d, tree: %d, tree_draw: %d, hexview: %d, ln: %d, ", (t + thv) * 1e6, t * 1e6, @draw_time * 1e6, thv * 1e6, @ln
61
+ puts "highlight = #{@cur_node.pos1}..#{@cur_node.pos2}"
62
+ #puts "keypress: #{c.inspect}"
63
+
64
+ begin
65
+ process_keypress
66
+ rescue EOFError => e
67
+ @ui.message_box_exception(e)
68
+ rescue Kaitai::Struct::Stream::UnexpectedDataError => e
69
+ @ui.message_box_exception(e)
70
+ end
71
+
72
+ return if @do_exit
73
+
74
+ clamp_cursor
75
+ }
76
+ end
77
+
78
+ def process_keypress
79
+ c = @ui.read_char_mapped
80
+ case c
81
+ when :up_arrow
82
+ @cur_line -= 1
83
+ @cur_node = nil
84
+ when :down_arrow
85
+ @cur_line += 1
86
+ @cur_node = nil
87
+ when :left_arrow
88
+ if @cur_node.open?
89
+ @cur_node.close
90
+ else
91
+ par = @cur_node.parent
92
+ if par
93
+ @cur_line = nil
94
+ @cur_node = par
95
+ end
96
+ end
97
+ when :right_arrow
98
+ if @cur_node.openable?
99
+ if @cur_node.open?
100
+ @cur_line += 1
101
+ @cur_node = nil
102
+ else
103
+ @cur_node.open
104
+ end
105
+ end
106
+ when :home
107
+ @cur_line = @cur_shift = 0
108
+ @cur_node = nil
109
+ when :end
110
+ @cur_line = @root.height - 1
111
+ @cur_node = nil
112
+ when :pg_up
113
+ @cur_line -= 20
114
+ @cur_node = nil
115
+ when :pg_dn
116
+ @cur_line += 20
117
+ @cur_node = nil
118
+ when :enter
119
+ if @cur_node.hex?
120
+ @ui.clear
121
+ hv = HexViewer.new(@ui, @cur_node.value)
122
+ hv.redraw
123
+ hv.run
124
+ @ui.clear
125
+ redraw
126
+ else
127
+ @cur_node.toggle
128
+ end
129
+ when :tab
130
+ @hv.run
131
+ when 'H'
132
+ @hv_hidden = !@hv_hidden
133
+ @ui.clear
134
+ redraw
135
+ when 'q'
136
+ @do_exit = true
137
+ end
138
+ end
139
+
140
+ def clamp_cursor
141
+ if @cur_line
142
+ @cur_line = 0 if @cur_line < 0
143
+
144
+ if @cur_line - @cur_shift < 0
145
+ @cur_shift = @cur_line
146
+ end
147
+ if @cur_line - @cur_shift > @max_scr_ln
148
+ @cur_shift = @cur_line - @max_scr_ln
149
+ end
150
+ end
151
+ end
152
+
153
+ def redraw
154
+ @draw_time = 0
155
+ Benchmark.realtime {
156
+ @ui.clear
157
+ @ln = 0
158
+ draw_rec(@root)
159
+ }
160
+ end
161
+
162
+ def draw_rec(n)
163
+ scr_ln = @ln - @cur_shift
164
+ return if @cur_node and scr_ln > @max_scr_ln
165
+
166
+ if @ln == @cur_line
167
+ # Seeking cur_node by cur_line
168
+ @cur_node = n
169
+ @ui.bg_color = :gray
170
+ @ui.fg_color = :black
171
+ elsif @cur_node == n
172
+ # Seeking cur_line by cur_node
173
+ @cur_line = @ln
174
+ @ui.bg_color = :gray
175
+ @ui.fg_color = :black
176
+ end
177
+
178
+ @draw_time += Benchmark.realtime {
179
+ # n.draw(@ui) if scr_ln >= 0
180
+ n.draw(@ui) if scr_ln >= 0 and scr_ln <= @max_scr_ln
181
+ }
182
+
183
+ @ui.reset_colors if @ln == @cur_line
184
+ @ln += 1
185
+ if n.open?
186
+ n.children.each { |ch|
187
+ draw_rec(ch)
188
+ break if scr_ln > @max_scr_ln
189
+ }
190
+ end
191
+ end
192
+
193
+ def do_exit
194
+ @do_exit = true
195
+ end
196
+
197
+ def hv_update_io
198
+ io = @cur_node.io
199
+ if io != @cur_io
200
+ @cur_io = io
201
+ io.seek(0)
202
+ buf = io.read_bytes_full
203
+ @hv.buf = buf
204
+
205
+ # @hv.redraw
206
+ end
207
+ end
208
+
209
+ def highlight_regions(max_levels)
210
+ node = @cur_node
211
+ r = []
212
+ max_levels.times { |i|
213
+ return r if node.nil?
214
+ r << [node.pos1, node.pos2]
215
+ node = node.parent
216
+ }
217
+ r
218
+ end
219
+
220
+ def tree_width
221
+ if @hv_hidden
222
+ @ui.cols
223
+ else
224
+ @hv_shift_x
225
+ end
226
+ end
227
+
228
+ def self.explore_object(obj, level)
229
+ root = Node.new(obj, level)
230
+ if obj.is_a?(Fixnum) or obj.is_a?(String)
231
+ # do nothing else
232
+ elsif obj.is_a?(Array)
233
+ root = Node.new(obj, level)
234
+ obj.each_with_index { |el, i|
235
+ n = explore_object(el, level + 1)
236
+ n.id = i
237
+ root.add(n)
238
+ }
239
+ else
240
+ root = Node.new(obj, level)
241
+ obj.instance_variables.each { |k|
242
+ k = k.to_s
243
+ next if k =~ /^@_/
244
+ el = obj.instance_eval(k)
245
+ n = explore_object(el, level + 1)
246
+ n.id = k
247
+ root.add(n)
248
+ }
249
+ end
250
+ root
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,7 @@
1
+ module Kaitai
2
+ module Struct
3
+ module Visualizer
4
+ VERSION = '0.3'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,47 @@
1
+ require 'tmpdir'
2
+
3
+ require 'kaitai/struct/visualizer/version'
4
+ require 'kaitai/tui'
5
+ require 'kaitai/struct/visualizer/tree'
6
+
7
+ # TODO: should be inside compiled files
8
+ require 'zlib'
9
+ require 'stringio'
10
+
11
+ module Kaitai::Struct::Visualizer
12
+ class Visualizer
13
+ def initialize(bin_fn, formats_fn)
14
+ @bin_fn = bin_fn
15
+ @formats_fn = formats_fn
16
+ @primary_format = @formats_fn.shift
17
+
18
+ main_class_name = compile_format(@primary_format)
19
+
20
+ @formats_fn.each { |fn|
21
+ compile_format(fn)
22
+ }
23
+
24
+ main_class = Kernel::const_get(main_class_name)
25
+ @data = main_class.from_file(@bin_fn)
26
+
27
+ load_exc = nil
28
+ begin
29
+ @data._read
30
+ rescue EOFError => e
31
+ load_exc = e
32
+ rescue Kaitai::Struct::Stream::UnexpectedDataError => e
33
+ load_exc = e
34
+ end
35
+
36
+ @ui = Kaitai::TUI.new
37
+ @tree = Tree.new(@ui, @data)
38
+
39
+ @tree.redraw
40
+ @ui.message_box_exception(load_exc) if load_exc
41
+ end
42
+
43
+ def run
44
+ @tree.run
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,24 @@
1
+ require 'kaitai/struct/visualizer/version'
2
+ require 'kaitai/struct/visualizer/visualizer'
3
+
4
+ module Kaitai::Struct::Visualizer
5
+
6
+ class ExternalCompilerVisualizer < Visualizer
7
+ def compile_format(fn)
8
+ main_class_name = nil
9
+ Dir.mktmpdir { |code_dir|
10
+ system("ksc -- --debug -t ruby '#{fn}' -d '#{code_dir}'")
11
+ exit $?.exitstatus if $?.exitstatus != 0
12
+
13
+ compiled_path = Dir.glob("#{code_dir}/*.rb")[0]
14
+
15
+ require compiled_path
16
+
17
+ main_class_name = File.readlines(compiled_path).grep(/^class /)[0].strip.gsub(/^class /, '').gsub(/ <.*$/, '')
18
+ }
19
+
20
+ return main_class_name
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,18 @@
1
+ require 'ks_ruby_compiler'
2
+
3
+ class RubyCompilerVisualizer extends Visualizer
4
+ def compile
5
+ Dir.mktmpdir { |code_dir|
6
+ compiled_path = "#{code_dir}/compiled.rb"
7
+ @compiler = CompileToRuby.new(@format_fn, compiled_path)
8
+ @compiler.compile
9
+
10
+ require compiled_path
11
+
12
+ main_class_name = @compiler.type2class(@compiler.desc['meta']['id'])
13
+ #puts "Main class: #{main_class_name}"
14
+ main_class = Kernel::const_get(main_class_name)
15
+ @data = main_class.from_file(@bin_fn)
16
+ }
17
+ end
18
+ end
data/lib/kaitai/tui.rb ADDED
@@ -0,0 +1,169 @@
1
+ # coding: utf-8
2
+ require 'io/console'
3
+
4
+ module Kaitai
5
+
6
+ class TUI
7
+ attr_reader :cols
8
+ attr_reader :rows
9
+
10
+ def initialize
11
+ @cols = `tput cols`.to_i
12
+ @rows = `tput lines`.to_i
13
+
14
+ @seq_clear = `tput clear`
15
+ @seq_sgr0 = `tput sgr0`
16
+ @seq_fgcolor = []
17
+ @seq_bgcolor = []
18
+ end
19
+
20
+ def clear
21
+ print @seq_clear
22
+ end
23
+
24
+ ##
25
+ # Put the cursor up to screen position (x, y). First line is 0,
26
+ # first column is 0.
27
+ def goto(x, y)
28
+ #print `tput cup #{y} #{x}`
29
+ printf "\e[%d;%dH", y + 1, x + 1
30
+ end
31
+
32
+ COLORS = {
33
+ :black => 0,
34
+ :gray => 7,
35
+ :gray0 => 232,
36
+ :gray1 => 233,
37
+ :gray2 => 234,
38
+ :gray3 => 235,
39
+ :gray4 => 236,
40
+ :gray5 => 237,
41
+ :gray6 => 238,
42
+ :gray7 => 239,
43
+ :gray8 => 240,
44
+ :gray9 => 241,
45
+ :gray10 => 242,
46
+ :gray11 => 243,
47
+ :gray12 => 244,
48
+ :gray13 => 245,
49
+ :gray14 => 246,
50
+ :gray15 => 247,
51
+ :gray16 => 248,
52
+ :gray17 => 249,
53
+ :gray18 => 250,
54
+ :gray19 => 251,
55
+ :gray20 => 252,
56
+ :gray21 => 253,
57
+ :gray22 => 254,
58
+ :gray23 => 255,
59
+ }
60
+
61
+ def fg_color=(col)
62
+ #print @seq_fgcolor[col] ||= `tput setaf #{col}`
63
+ code = COLORS[col]
64
+ raise "Invalid color: #{col}" unless code
65
+ print "\e[38;5;#{code}m"
66
+ end
67
+
68
+ def bg_color=(col)
69
+ #print @seq_bgcolor[col] ||= `tput setab #{col}`
70
+ code = COLORS[col]
71
+ raise "Invalid color: #{col}" unless code
72
+ print "\e[48;5;#{code}m"
73
+ end
74
+
75
+ def reset_colors
76
+ print @seq_sgr0
77
+ end
78
+
79
+ # Reads keypresses from the user including 2 and 3 escape character sequences.
80
+ def read_char
81
+ $stdin.echo = false
82
+ $stdin.raw!
83
+
84
+ input = $stdin.getc.chr
85
+ if input == "\e" then
86
+ input << $stdin.read_nonblock(3) rescue nil
87
+ input << $stdin.read_nonblock(2) rescue nil
88
+ end
89
+ ensure
90
+ $stdin.echo = true
91
+ $stdin.cooked!
92
+
93
+ return input
94
+ end
95
+
96
+ def read_char_mapped
97
+ c = read_char
98
+ c2 = KEY_MAP[c]
99
+ c2 ? c2 : c
100
+ end
101
+
102
+ KEY_MAP = {
103
+ "\t" => :tab,
104
+ "\r" => :enter,
105
+ "\e[A" => :up_arrow,
106
+ "\e[B" => :down_arrow,
107
+ "\e[C" => :right_arrow,
108
+ "\e[D" => :left_arrow,
109
+ "\e[5~" => :pg_up,
110
+ "\e[6~" => :pg_dn,
111
+ "\e[H" => :home,
112
+ "\e[F" => :end,
113
+ }
114
+
115
+ def message_box_exception(e)
116
+ message_box("Error while parsing", e.message)
117
+ end
118
+
119
+ SINGLE_CHARSET = '┌┐└┘─│'
120
+ HEAVY_CHARSET = '┏┓┗┛━┃'
121
+ DOUBLE_CHARSET = '╔╗╚╝═║'
122
+
123
+ CHAR_TL = 0
124
+ CHAR_TR = 1
125
+ CHAR_BL = 2
126
+ CHAR_BR = 3
127
+ CHAR_H = 4
128
+ CHAR_V = 5
129
+
130
+ def message_box(header, msg)
131
+ top_y = @rows / 2 - 5
132
+ draw_rectangle(10, top_y, @cols - 20, 10)
133
+ goto(@cols / 2 - (header.length / 2) - 1, top_y)
134
+ print ' ', header, ' '
135
+ goto(11, top_y + 1)
136
+ puts msg
137
+ draw_button(@cols / 2 - 10, top_y + 8, 10, 'OK')
138
+ loop {
139
+ c = read_char_mapped
140
+ return if c == :enter
141
+ }
142
+ end
143
+
144
+ def draw_rectangle(x, y, w, h, charset = DOUBLE_CHARSET)
145
+ goto(x, y)
146
+ print charset[CHAR_TL]
147
+ print charset[CHAR_H] * (w - 2)
148
+ print charset[CHAR_TR]
149
+
150
+ ((y + 1)..(y + h - 1)).each { |i|
151
+ goto(x, i)
152
+ print charset[CHAR_V]
153
+ print ' ' * (w - 2)
154
+ print charset[CHAR_V]
155
+ }
156
+
157
+ goto(x, y + h)
158
+ print charset[CHAR_BL]
159
+ print charset[CHAR_H] * (w - 2)
160
+ print charset[CHAR_BR]
161
+ end
162
+
163
+ def draw_button(x, y, w, caption)
164
+ goto(x, y)
165
+ puts "[ #{caption} ]"
166
+ end
167
+ end
168
+
169
+ end