metasm 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (192) hide show
  1. data/BUGS +11 -0
  2. data/CREDITS +17 -0
  3. data/README +270 -0
  4. data/TODO +114 -0
  5. data/doc/code_organisation.txt +146 -0
  6. data/doc/const_missing.txt +16 -0
  7. data/doc/core_classes.txt +75 -0
  8. data/doc/feature_list.txt +53 -0
  9. data/doc/index.txt +59 -0
  10. data/doc/install_notes.txt +170 -0
  11. data/doc/style.css +3 -0
  12. data/doc/use_cases.txt +18 -0
  13. data/lib/metasm.rb +80 -0
  14. data/lib/metasm/arm.rb +12 -0
  15. data/lib/metasm/arm/debug.rb +39 -0
  16. data/lib/metasm/arm/decode.rb +167 -0
  17. data/lib/metasm/arm/encode.rb +77 -0
  18. data/lib/metasm/arm/main.rb +75 -0
  19. data/lib/metasm/arm/opcodes.rb +177 -0
  20. data/lib/metasm/arm/parse.rb +130 -0
  21. data/lib/metasm/arm/render.rb +55 -0
  22. data/lib/metasm/compile_c.rb +1457 -0
  23. data/lib/metasm/dalvik.rb +8 -0
  24. data/lib/metasm/dalvik/decode.rb +196 -0
  25. data/lib/metasm/dalvik/main.rb +60 -0
  26. data/lib/metasm/dalvik/opcodes.rb +366 -0
  27. data/lib/metasm/decode.rb +213 -0
  28. data/lib/metasm/decompile.rb +2659 -0
  29. data/lib/metasm/disassemble.rb +2068 -0
  30. data/lib/metasm/disassemble_api.rb +1280 -0
  31. data/lib/metasm/dynldr.rb +1329 -0
  32. data/lib/metasm/encode.rb +333 -0
  33. data/lib/metasm/exe_format/a_out.rb +194 -0
  34. data/lib/metasm/exe_format/autoexe.rb +82 -0
  35. data/lib/metasm/exe_format/bflt.rb +189 -0
  36. data/lib/metasm/exe_format/coff.rb +455 -0
  37. data/lib/metasm/exe_format/coff_decode.rb +901 -0
  38. data/lib/metasm/exe_format/coff_encode.rb +1078 -0
  39. data/lib/metasm/exe_format/dex.rb +457 -0
  40. data/lib/metasm/exe_format/dol.rb +145 -0
  41. data/lib/metasm/exe_format/elf.rb +923 -0
  42. data/lib/metasm/exe_format/elf_decode.rb +979 -0
  43. data/lib/metasm/exe_format/elf_encode.rb +1375 -0
  44. data/lib/metasm/exe_format/macho.rb +827 -0
  45. data/lib/metasm/exe_format/main.rb +228 -0
  46. data/lib/metasm/exe_format/mz.rb +164 -0
  47. data/lib/metasm/exe_format/nds.rb +172 -0
  48. data/lib/metasm/exe_format/pe.rb +437 -0
  49. data/lib/metasm/exe_format/serialstruct.rb +246 -0
  50. data/lib/metasm/exe_format/shellcode.rb +114 -0
  51. data/lib/metasm/exe_format/xcoff.rb +167 -0
  52. data/lib/metasm/gui.rb +23 -0
  53. data/lib/metasm/gui/cstruct.rb +373 -0
  54. data/lib/metasm/gui/dasm_coverage.rb +199 -0
  55. data/lib/metasm/gui/dasm_decomp.rb +369 -0
  56. data/lib/metasm/gui/dasm_funcgraph.rb +103 -0
  57. data/lib/metasm/gui/dasm_graph.rb +1354 -0
  58. data/lib/metasm/gui/dasm_hex.rb +543 -0
  59. data/lib/metasm/gui/dasm_listing.rb +599 -0
  60. data/lib/metasm/gui/dasm_main.rb +906 -0
  61. data/lib/metasm/gui/dasm_opcodes.rb +291 -0
  62. data/lib/metasm/gui/debug.rb +1228 -0
  63. data/lib/metasm/gui/gtk.rb +884 -0
  64. data/lib/metasm/gui/qt.rb +495 -0
  65. data/lib/metasm/gui/win32.rb +3004 -0
  66. data/lib/metasm/gui/x11.rb +621 -0
  67. data/lib/metasm/ia32.rb +14 -0
  68. data/lib/metasm/ia32/compile_c.rb +1523 -0
  69. data/lib/metasm/ia32/debug.rb +193 -0
  70. data/lib/metasm/ia32/decode.rb +1167 -0
  71. data/lib/metasm/ia32/decompile.rb +564 -0
  72. data/lib/metasm/ia32/encode.rb +314 -0
  73. data/lib/metasm/ia32/main.rb +233 -0
  74. data/lib/metasm/ia32/opcodes.rb +872 -0
  75. data/lib/metasm/ia32/parse.rb +327 -0
  76. data/lib/metasm/ia32/render.rb +91 -0
  77. data/lib/metasm/main.rb +1193 -0
  78. data/lib/metasm/mips.rb +11 -0
  79. data/lib/metasm/mips/compile_c.rb +7 -0
  80. data/lib/metasm/mips/decode.rb +253 -0
  81. data/lib/metasm/mips/encode.rb +51 -0
  82. data/lib/metasm/mips/main.rb +72 -0
  83. data/lib/metasm/mips/opcodes.rb +443 -0
  84. data/lib/metasm/mips/parse.rb +51 -0
  85. data/lib/metasm/mips/render.rb +43 -0
  86. data/lib/metasm/os/gnu_exports.rb +270 -0
  87. data/lib/metasm/os/linux.rb +1112 -0
  88. data/lib/metasm/os/main.rb +1686 -0
  89. data/lib/metasm/os/remote.rb +527 -0
  90. data/lib/metasm/os/windows.rb +2027 -0
  91. data/lib/metasm/os/windows_exports.rb +745 -0
  92. data/lib/metasm/parse.rb +876 -0
  93. data/lib/metasm/parse_c.rb +3938 -0
  94. data/lib/metasm/pic16c/decode.rb +42 -0
  95. data/lib/metasm/pic16c/main.rb +17 -0
  96. data/lib/metasm/pic16c/opcodes.rb +68 -0
  97. data/lib/metasm/ppc.rb +11 -0
  98. data/lib/metasm/ppc/decode.rb +264 -0
  99. data/lib/metasm/ppc/decompile.rb +251 -0
  100. data/lib/metasm/ppc/encode.rb +51 -0
  101. data/lib/metasm/ppc/main.rb +129 -0
  102. data/lib/metasm/ppc/opcodes.rb +410 -0
  103. data/lib/metasm/ppc/parse.rb +52 -0
  104. data/lib/metasm/preprocessor.rb +1277 -0
  105. data/lib/metasm/render.rb +130 -0
  106. data/lib/metasm/sh4.rb +8 -0
  107. data/lib/metasm/sh4/decode.rb +336 -0
  108. data/lib/metasm/sh4/main.rb +292 -0
  109. data/lib/metasm/sh4/opcodes.rb +381 -0
  110. data/lib/metasm/x86_64.rb +12 -0
  111. data/lib/metasm/x86_64/compile_c.rb +1025 -0
  112. data/lib/metasm/x86_64/debug.rb +59 -0
  113. data/lib/metasm/x86_64/decode.rb +268 -0
  114. data/lib/metasm/x86_64/encode.rb +264 -0
  115. data/lib/metasm/x86_64/main.rb +135 -0
  116. data/lib/metasm/x86_64/opcodes.rb +118 -0
  117. data/lib/metasm/x86_64/parse.rb +68 -0
  118. data/misc/bottleneck.rb +61 -0
  119. data/misc/cheader-findpppath.rb +58 -0
  120. data/misc/hexdiff.rb +74 -0
  121. data/misc/hexdump.rb +55 -0
  122. data/misc/metasm-all.rb +13 -0
  123. data/misc/objdiff.rb +47 -0
  124. data/misc/objscan.rb +40 -0
  125. data/misc/pdfparse.rb +661 -0
  126. data/misc/ppc_pdf2oplist.rb +192 -0
  127. data/misc/tcp_proxy_hex.rb +84 -0
  128. data/misc/txt2html.rb +440 -0
  129. data/samples/a.out.rb +31 -0
  130. data/samples/asmsyntax.rb +77 -0
  131. data/samples/bindiff.rb +555 -0
  132. data/samples/compilation-steps.rb +49 -0
  133. data/samples/cparser_makestackoffset.rb +55 -0
  134. data/samples/dasm-backtrack.rb +38 -0
  135. data/samples/dasmnavig.rb +318 -0
  136. data/samples/dbg-apihook.rb +228 -0
  137. data/samples/dbghelp.rb +143 -0
  138. data/samples/disassemble-gui.rb +102 -0
  139. data/samples/disassemble.rb +133 -0
  140. data/samples/dump_upx.rb +95 -0
  141. data/samples/dynamic_ruby.rb +1929 -0
  142. data/samples/elf_list_needed.rb +46 -0
  143. data/samples/elf_listexports.rb +33 -0
  144. data/samples/elfencode.rb +25 -0
  145. data/samples/exeencode.rb +128 -0
  146. data/samples/factorize-headers-elfimports.rb +77 -0
  147. data/samples/factorize-headers-peimports.rb +109 -0
  148. data/samples/factorize-headers.rb +43 -0
  149. data/samples/gdbclient.rb +583 -0
  150. data/samples/generate_libsigs.rb +102 -0
  151. data/samples/hotfix_gtk_dbg.rb +59 -0
  152. data/samples/install_win_env.rb +78 -0
  153. data/samples/lindebug.rb +924 -0
  154. data/samples/linux_injectsyscall.rb +95 -0
  155. data/samples/machoencode.rb +31 -0
  156. data/samples/metasm-shell.rb +91 -0
  157. data/samples/pe-hook.rb +69 -0
  158. data/samples/pe-ia32-cpuid.rb +203 -0
  159. data/samples/pe-mips.rb +35 -0
  160. data/samples/pe-shutdown.rb +78 -0
  161. data/samples/pe-testrelocs.rb +51 -0
  162. data/samples/pe-testrsrc.rb +24 -0
  163. data/samples/pe_listexports.rb +31 -0
  164. data/samples/peencode.rb +19 -0
  165. data/samples/peldr.rb +494 -0
  166. data/samples/preprocess-flatten.rb +19 -0
  167. data/samples/r0trace.rb +308 -0
  168. data/samples/rubstop.rb +399 -0
  169. data/samples/scan_pt_gnu_stack.rb +54 -0
  170. data/samples/scanpeexports.rb +62 -0
  171. data/samples/shellcode-c.rb +40 -0
  172. data/samples/shellcode-dynlink.rb +146 -0
  173. data/samples/source.asm +34 -0
  174. data/samples/struct_offset.rb +47 -0
  175. data/samples/testpe.rb +32 -0
  176. data/samples/testraw.rb +45 -0
  177. data/samples/win32genloader.rb +132 -0
  178. data/samples/win32hooker-advanced.rb +169 -0
  179. data/samples/win32hooker.rb +96 -0
  180. data/samples/win32livedasm.rb +33 -0
  181. data/samples/win32remotescan.rb +133 -0
  182. data/samples/wintrace.rb +92 -0
  183. data/tests/all.rb +8 -0
  184. data/tests/dasm.rb +39 -0
  185. data/tests/dynldr.rb +35 -0
  186. data/tests/encodeddata.rb +132 -0
  187. data/tests/ia32.rb +82 -0
  188. data/tests/mips.rb +116 -0
  189. data/tests/parse_c.rb +239 -0
  190. data/tests/preprocessor.rb +269 -0
  191. data/tests/x86_64.rb +62 -0
  192. metadata +255 -0
@@ -0,0 +1,884 @@
1
+ # This file is part of Metasm, the Ruby assembly manipulation suite
2
+ # Copyright (C) 2006-2009 Yoann GUILLOT
3
+ #
4
+ # Licence is LGPL, see LICENCE in the top-level directory
5
+
6
+
7
+ require 'gtk2'
8
+
9
+ module Metasm
10
+ module Gui
11
+
12
+ module Protect
13
+ @@lasterror = Time.now
14
+ def protect
15
+ yield
16
+ rescue Object
17
+ puts $!.message, $!.backtrace # also dump on stdout, for c/c
18
+ delay = Time.now-@@lasterror
19
+ sleep 1-delay if delay < 1 # msgbox flood protection
20
+ @@lasterror = Time.now
21
+ messagebox([$!.message, $!.backtrace].join("\n"), $!.class.name)
22
+ end
23
+ end
24
+
25
+ module Msgbox
26
+ include Protect
27
+
28
+ def toplevel
29
+ if self.kind_of? Gtk::Window
30
+ self
31
+ else
32
+ super()
33
+ end
34
+ end
35
+
36
+ # shows a message box (non-modal)
37
+ # args: message, title/optionhash
38
+ def messagebox(*a)
39
+ MessageBox.new(toplevel, *a)
40
+ end
41
+
42
+ # asks for user input, yields the result (unless 'cancel' clicked)
43
+ # args: prompt, :text => default text, :title => title
44
+ def inputbox(*a)
45
+ InputBox.new(toplevel, *a) { |*ya| protect { yield(*ya) } }
46
+ end
47
+
48
+ # asks to chose a file to open, yields filename
49
+ # args: title, :path => path
50
+ def openfile(*a)
51
+ OpenFile.new(toplevel, *a) { |*ya| protect { yield(*ya) } }
52
+ end
53
+
54
+ # same as openfile, but for writing a (new) file
55
+ def savefile(*a)
56
+ SaveFile.new(toplevel, *a) { |*ya| protect { yield(*ya) } }
57
+ end
58
+
59
+ # displays a popup showing a table, yields the selected row
60
+ # args: title, [[col0 title, col1 title...], [col0 val0, col1 val0...], [val1], [val2]...]
61
+ def listwindow(*a)
62
+ ListWindow.new(toplevel, *a) { |*ya| protect { yield(*ya) } }
63
+ end
64
+ end
65
+
66
+ # clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
67
+ # text = clipboard.wait_for_text
68
+ # clipboard.text = 'foo'
69
+
70
+ # a widget that holds many other widgets, and displays only one of them at a time
71
+ class ContainerChoiceWidget < Gtk::Notebook
72
+ include Msgbox
73
+
74
+ attr_accessor :views, :view_indexes
75
+ def initialize(*a, &b)
76
+ super()
77
+ self.show_border = false
78
+ self.show_tabs = false
79
+ @views = {}
80
+ @view_indexes = []
81
+
82
+ signal_connect('realize') { initialize_visible } if respond_to? :initialize_visible
83
+
84
+ initialize_widget(*a, &b)
85
+
86
+ show_all
87
+ end
88
+
89
+ def view(i)
90
+ @views[i]
91
+ end
92
+
93
+ def showview(i)
94
+ set_page @view_indexes.index(i)
95
+ end
96
+
97
+ def addview(name, w)
98
+ @view_indexes << name
99
+ @views[name] = w
100
+ append_page(w, Gtk::Label.new(name.to_s))
101
+ end
102
+
103
+ def curview
104
+ @views[curview_index]
105
+ end
106
+
107
+ def curview_index
108
+ return if page == -1
109
+ @view_indexes[page]
110
+ end
111
+ end
112
+
113
+ class ContainerVBoxWidget < Gtk::VBox
114
+ include Msgbox
115
+
116
+ def initialize(*a, &b)
117
+ super()
118
+
119
+ signal_connect('realize') { initialize_visible } if respond_to? :initialize_visible
120
+
121
+ signal_connect('size_request') { |w, alloc| resize(*alloc) } if respond_to? :resize
122
+
123
+ self.spacing = 2
124
+
125
+ initialize_widget(*a, &b)
126
+ end
127
+
128
+ def resize_child(cld, w, h)
129
+ pk = query_child_packing(cld)
130
+ if h <= 0
131
+ pk[0] = true
132
+ h = 1
133
+ else
134
+ pk[0] = false
135
+ end
136
+ return if h == cld.allocation.height
137
+ set_child_packing(cld, *pk)
138
+ cld.set_height_request(h)
139
+ end
140
+
141
+ def redraw
142
+ end
143
+ end
144
+
145
+ class DrawableWidget < Gtk::DrawingArea
146
+ include Msgbox
147
+
148
+ attr_accessor :parent_widget, :caret_x, :caret_y, :hl_word
149
+ # this hash is used to determine the colors of the Gui elements (background, caret, ...)
150
+ # modifications to it are only useful before the widget is first rendered (IE before Gui.main)
151
+ attr_accessor :default_color_association
152
+
153
+ # keypress event keyval traduction table
154
+ Keyboard_trad = Gdk::Keyval.constants.grep(/^GDK_/).inject({}) { |h, cst|
155
+ v = Gdk::Keyval.const_get(cst)
156
+ key = cst.to_s.sub(/^GDK_/, '').sub(/^KP_/, '')
157
+ if key.length == 1
158
+ key = key[0] # ?a, ?b etc
159
+ else
160
+ key = key.downcase.to_sym
161
+ key = {
162
+ :page_up => :pgup, :page_down => :pgdown, :next => :pgdown,
163
+ :escape => :esc, :return => :enter, :l1 => :f11, :l2 => :f12,
164
+ :prior => :pgup,
165
+
166
+ :space => ?\ ,
167
+ :asciitilde => ?~, :quoteleft => ?`,
168
+ :exclam => ?!, :at => ?@,
169
+ :numbersign => ?#, :dollar => ?$,
170
+ :percent => ?%, :asciicircum => ?^,
171
+ :ampersand => ?&, :asterisk => ?*,
172
+ :parenleft => ?(, :parenright => ?),
173
+ :bracketleft => ?[, :bracketright => ?],
174
+ :braceleft => ?{, :braceright => ?},
175
+ :less => ?<, :greater => ?>,
176
+ :quotedbl => ?", :quoteright => ?',
177
+ :coma => ?,, :period => ?.,
178
+ :colon => ?:, :semicolon => ?;,
179
+ :slash => ?/, :equal => ?=,
180
+ :plus => ?+, :minus => ?-,
181
+ :question => ??, :backslash => ?\\,
182
+ :underscore => ?_, :bar => ?|,
183
+ :comma => ?,,
184
+ :divide => ?/, :multiply => ?*,
185
+ :subtract => ?-, :add => ?+
186
+ }.fetch(key, key)
187
+ end
188
+
189
+ h.update v => key
190
+ }
191
+
192
+ def initialize(*a, &b)
193
+ @parent_widget = nil
194
+
195
+ @caret_x = @caret_y = 0 # text cursor position
196
+ @oldcaret_x = @oldcaret_y = 1
197
+ @hl_word = nil
198
+
199
+ @layout = Pango::Layout.new Gdk::Pango.context # text rendering
200
+
201
+ @color = {}
202
+ @default_color_association = {:background => :palegrey}
203
+
204
+ super()
205
+
206
+ # events callbacks
207
+ signal_connect('expose_event') {
208
+ @w = window ; @gc = Gdk::GC.new(@w)
209
+ protect { paint }
210
+ @w = @gc = nil
211
+ true
212
+ }
213
+
214
+ signal_connect('size_allocate') { |w, alloc|
215
+ protect { resized(alloc.width, alloc.height) }
216
+ }
217
+
218
+ signal_connect('button_press_event') { |w, ev|
219
+ @last_kb_ev = ev
220
+ if keyboard_state(:control)
221
+ next protect { click_ctrl(ev.x, ev.y) } if ev.event_type == Gdk::Event::Type::BUTTON_PRESS and ev.button == 1 and respond_to? :click_ctrl
222
+ next
223
+ end
224
+ case ev.event_type
225
+ when Gdk::Event::Type::BUTTON_PRESS
226
+ grab_focus
227
+ case ev.button
228
+ when 1; protect { click(ev.x, ev.y) } if respond_to? :click
229
+ when 3; protect { rightclick(ev.x, ev.y) } if respond_to? :rightclick
230
+ end
231
+ when Gdk::Event::Type::BUTTON2_PRESS
232
+ case ev.button
233
+ when 1; protect { doubleclick(ev.x, ev.y) } if respond_to? :doubleclick
234
+ end
235
+ end
236
+ }
237
+
238
+ signal_connect('motion_notify_event') { |w, ev|
239
+ @last_kb_ev = ev
240
+ if keyboard_state(:control)
241
+ protect { mousemove_ctrl(ev.x, ev.y) } if respond_to? :mousemove_ctrl
242
+ else
243
+ protect { mousemove(ev.x, ev.y) }
244
+ end
245
+ } if respond_to? :mousemove
246
+
247
+ signal_connect('button_release_event') { |w, ev|
248
+ protect { mouserelease(ev.x, ev.y) } if ev.button == 1
249
+ } if respond_to? :mouserelease
250
+
251
+ signal_connect('scroll_event') { |w, ev|
252
+ dir = case ev.direction
253
+ when Gdk::EventScroll::Direction::UP; :up
254
+ when Gdk::EventScroll::Direction::DOWN; :down
255
+ else next
256
+ end
257
+ @last_kb_ev = ev
258
+ if keyboard_state(:control)
259
+ protect { mouse_wheel_ctrl(dir, ev.x, ev.y) } if respond_to? :mouse_wheel_ctrl
260
+ else
261
+ protect { mouse_wheel(dir, ev.x, ev.y) }
262
+ end
263
+ } if respond_to? :mouse_wheel
264
+
265
+ signal_connect('key_press_event') { |w, ev|
266
+ @last_kb_ev = ev
267
+ key = Keyboard_trad[ev.keyval]
268
+ if keyboard_state(:control)
269
+ protect { keypress_ctrl(key) or (@parent_widget and @parent_widget.keypress_ctrl(key)) }
270
+ else
271
+ protect { keypress(key) or (@parent_widget and @parent_widget.keypress(key)) }
272
+ end
273
+ }
274
+
275
+ signal_connect('realize') {
276
+ { :white => 'fff', :palegrey => 'ddd', :black => '000', :grey => '444',
277
+ :red => 'f00', :darkred => '800', :palered => 'fcc',
278
+ :green => '0f0', :darkgreen => '080', :palegreen => 'cfc',
279
+ :blue => '00f', :darkblue => '008', :paleblue => 'ccf',
280
+ :yellow => 'ff0', :darkyellow => '440', :paleyellow => 'ffc',
281
+ }.each { |tag, val|
282
+ @color[tag] = color(val)
283
+ }
284
+
285
+ set_color_association @default_color_association
286
+
287
+ initialize_visible if respond_to? :initialize_visible
288
+ }
289
+
290
+ initialize_widget(*a, &b)
291
+
292
+ # receive keyboard/mouse signals
293
+ set_events Gdk::Event::ALL_EVENTS_MASK
294
+ set_can_focus true
295
+ set_font 'courier 10'
296
+ end
297
+
298
+ def initialize_widget
299
+ end
300
+
301
+
302
+ # create a color from a 'rgb' description
303
+ def color(val)
304
+ if not @color[val]
305
+ @color[val] = Gdk::Color.new(*val.unpack('CCC').map { |c| (c.chr*4).hex })
306
+ window.colormap.alloc_color(@color[val], true, true)
307
+ end
308
+ @color[val]
309
+ end
310
+
311
+ def set_caret_from_click(x, y)
312
+ @caret_x = (x-1).to_i / @font_width
313
+ @caret_y = y.to_i / @font_height
314
+ update_caret
315
+ end
316
+
317
+ # change the font of the widget
318
+ # arg is a Gtk Fontdescription string (eg 'courier 10')
319
+ def set_font(descr)
320
+ @layout.font_description = Pango::FontDescription.new(descr)
321
+ @layout.text = 'x'
322
+ @font_width, @font_height = @layout.pixel_size
323
+ gui_update
324
+ end
325
+
326
+ # change the color association
327
+ # arg is a hash function symbol => color symbol
328
+ # color must be allocated
329
+ # check #initialize/sig('realize') for initial function/color list
330
+ def set_color_association(hash)
331
+ hash.each { |k, v| @color[k] = color(v) }
332
+ modify_bg Gtk::STATE_NORMAL, @color[:background]
333
+ gui_update
334
+ end
335
+
336
+ # update @hl_word from a line & offset, return nil if unchanged
337
+ def update_hl_word(line, offset)
338
+ return if not line
339
+ word = line[0...offset].to_s[/\w*$/] << line[offset..-1].to_s[/^\w*/]
340
+ word = nil if word == ''
341
+ @hl_word = word if @hl_word != word
342
+ end
343
+
344
+ def paint
345
+ end
346
+
347
+ # invalidate the whole widget area
348
+ def redraw
349
+ invalidate(0, 0, 1000000, 1000000)
350
+ end
351
+
352
+ def invalidate_caret(cx, cy, x=0, y=0)
353
+ invalidate(x + cx*@font_width, y + cy*@font_height, 2, @font_height)
354
+ end
355
+
356
+ def invalidate(x, y, w, h)
357
+ return if not window
358
+ window.invalidate Gdk::Rectangle.new(x, y, w, h), false
359
+ end
360
+
361
+ def width
362
+ allocation.width
363
+ end
364
+
365
+ def height
366
+ allocation.height
367
+ end
368
+
369
+ def resized(w, h)
370
+ redraw
371
+ end
372
+
373
+ def keypress(key)
374
+ end
375
+
376
+ def keypress_ctrl(key)
377
+ end
378
+
379
+ def gui_update
380
+ redraw
381
+ end
382
+
383
+ def draw_color(col)
384
+ @gc.set_foreground color(col)
385
+ end
386
+
387
+ def draw_rectangle(x, y, w, h)
388
+ @w.draw_rectangle(@gc, true, x, y, w, h)
389
+ end
390
+
391
+ def draw_rectangle_color(col, x, y, w, h)
392
+ draw_color(col)
393
+ draw_rectangle(x, y, w, h)
394
+ end
395
+
396
+ def draw_line(x, y, ex, ey)
397
+ @w.draw_line(@gc, x, y, ex, ey)
398
+ end
399
+
400
+ def draw_line_color(col, x, y, ex, ey)
401
+ draw_color(col)
402
+ draw_line(x, y, ex, ey)
403
+ end
404
+
405
+ def draw_string(x, y, str)
406
+ @layout.text = str
407
+ @w.draw_layout(@gc, x, y, @layout)
408
+ end
409
+
410
+ def draw_string_color(col, x, y, str)
411
+ draw_color(col)
412
+ draw_string(x, y, str)
413
+ end
414
+
415
+ def clipboard_copy(buf)
416
+ clipboard = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
417
+ clipboard.text = buf
418
+ end
419
+
420
+ def clipboard_paste
421
+ clipboard = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
422
+ clipboard.wait_for_text
423
+ end
424
+
425
+ def keyboard_state(query=nil)
426
+ case query
427
+ when :control, :ctrl
428
+ ev = @last_kb_ev and ev.state & Gdk::Window::CONTROL_MASK == Gdk::Window::CONTROL_MASK
429
+ when :shift
430
+ ev = @last_kb_ev and ev.state & Gdk::Window::SHIFT_MASK == Gdk::Window::SHIFT_MASK
431
+ when :alt
432
+ ev = @last_kb_ev and ev.state & Gdk::Window::MOD1_MASK == Gdk::Window::MOD1_MASK
433
+ else
434
+ [:control, :shift, :alt].find_all { |s| keyboard_state(s) }
435
+ end
436
+ end
437
+ end
438
+
439
+ module WindowPos
440
+ def x; position[0]; end
441
+ def x=(nx); move(nx, position[1]); end
442
+ def y; position[1]; end
443
+ def y=(ny); move(position[0], ny); end
444
+ def width; size[0] ; end
445
+ def width=(nw); resize(nw, size[1]); end
446
+ def height; size[1] ; end
447
+ def height=(nh); resize(size[0], nh); end
448
+ end
449
+
450
+ class MessageBox < Gtk::MessageDialog
451
+ include WindowPos
452
+ def initialize(owner, str, opts={})
453
+ owner = nil if owner and (not owner.kind_of? Gtk::Window or owner.destroyed?)
454
+ owner ||= Gtk::Window.toplevels.first
455
+ opts = {:title => opts} if opts.kind_of? String
456
+ super(owner, Gtk::Dialog::DESTROY_WITH_PARENT, INFO, BUTTONS_CLOSE, str)
457
+ self.title = opts[:title] if opts[:title]
458
+ signal_connect('response') { destroy }
459
+ show_all
460
+ present # bring the window to the foreground & set focus
461
+ end
462
+ end
463
+
464
+ class InputBox < Gtk::Dialog
465
+ include WindowPos
466
+ attr_accessor :label, :textwidget
467
+
468
+ # shows a simplitic input box (eg window with a 1-line textbox + OK button), yields the text
469
+ # TODO history, dropdown, autocomplete, contexts, 3D stereo surround, etc
470
+ def initialize(owner, str, opts={})
471
+ owner ||= Gtk::Window.toplevels.first
472
+ super(nil, owner, Gtk::Dialog::DESTROY_WITH_PARENT,
473
+ [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_ACCEPT], [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_REJECT])
474
+ self.title = opts[:title] if opts[:title]
475
+
476
+ @label = Gtk::Label.new(str)
477
+ @textwidget = Gtk::TextView.new
478
+ if opts[:text]
479
+ @textwidget.buffer.text = opts[:text].to_s
480
+ @textwidget.buffer.move_mark('selection_bound', @textwidget.buffer.start_iter)
481
+ @textwidget.buffer.move_mark('insert', @textwidget.buffer.end_iter)
482
+ end
483
+
484
+ @textwidget.signal_connect('key_press_event') { |w, ev|
485
+ key = DrawableWidget::Keyboard_trad[ev.keyval]
486
+ case key
487
+ when :escape; response(RESPONSE_REJECT) ; true
488
+ when :enter; response(RESPONSE_ACCEPT) ; true
489
+ end
490
+ }
491
+
492
+ signal_connect('response') { |win, id|
493
+ resp = @textwidget.buffer.text if id == RESPONSE_ACCEPT
494
+ destroy
495
+ yield resp.strip if resp
496
+ true
497
+ }
498
+
499
+ vbox.pack_start label, false, false, 8
500
+ vbox.pack_start @textwidget, false, false, 8
501
+
502
+ Gtk::Drag.dest_set(self,
503
+ Gtk::Drag::DEST_DEFAULT_MOTION |
504
+ Gtk::Drag::DEST_DEFAULT_DROP,
505
+ [['text/plain', 0, 0], ['text/uri-list', 0, 0]],
506
+ Gdk::DragContext::ACTION_COPY | Gdk::DragContext::ACTION_MOVE)
507
+
508
+ signal_connect('drag_data_received') { |w, dc, x, y, data, info, time|
509
+ dc.targets.each { |target|
510
+ next if target.name != 'text/plain' and target.name != 'text/uri-list'
511
+ data.data.each_line { |l|
512
+ l = l.chomp.sub(%r{^file://}, '')
513
+ self.text = l
514
+ }
515
+ }
516
+ Gtk::Drag.finish(dc, true, false, time)
517
+ }
518
+
519
+
520
+ show_all
521
+ present
522
+ end
523
+
524
+ def text ; @textwidget.buffer.text ; end
525
+ def text=(nt) ; @textwidget.buffer.text = nt ; end
526
+ end
527
+
528
+ class OpenFile < Gtk::FileChooserDialog
529
+ include WindowPos
530
+ @@currentfolder = nil
531
+
532
+ # shows an asynchronous FileChooser window, yields the chosen filename
533
+ # TODO save last path
534
+ def initialize(owner, title, opts={})
535
+ owner ||= Gtk::Window.toplevels.first
536
+ super(title, owner, Gtk::FileChooser::ACTION_OPEN, nil,
537
+ [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL], [Gtk::Stock::OPEN, Gtk::Dialog::RESPONSE_ACCEPT])
538
+ f = opts[:path] || @@currentfolder
539
+ self.current_folder = f if f
540
+ signal_connect('response') { |win, id|
541
+ if id == Gtk::Dialog::RESPONSE_ACCEPT
542
+ file = filename
543
+ @@currentfolder = File.dirname(file)
544
+ end
545
+ destroy
546
+ yield file if file
547
+ true
548
+ }
549
+
550
+ show_all
551
+ present
552
+ end
553
+ end
554
+
555
+ class SaveFile < Gtk::FileChooserDialog
556
+ include WindowPos
557
+ @@currentfolder = nil
558
+
559
+ # shows an asynchronous FileChooser window, yields the chosen filename
560
+ # TODO save last path
561
+ def initialize(owner, title, opts={})
562
+ owner ||= Gtk::Window.toplevels.first
563
+ super(title, owner, Gtk::FileChooser::ACTION_SAVE, nil,
564
+ [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL], [Gtk::Stock::SAVE, Gtk::Dialog::RESPONSE_ACCEPT])
565
+ f = opts[:path] || @@currentfolder
566
+ self.current_folder = f if f
567
+ signal_connect('response') { |win, id|
568
+ if id == Gtk::Dialog::RESPONSE_ACCEPT
569
+ file = filename
570
+ @@currentfolder = File.dirname(file)
571
+ end
572
+ destroy
573
+ yield file if file
574
+ true
575
+ }
576
+
577
+ show_all
578
+ present
579
+ end
580
+ end
581
+
582
+ class ListWindow < Gtk::Dialog
583
+ include WindowPos
584
+ # shows a window with a list of items
585
+ # the list is an array of arrays, displayed as String
586
+ # the first array is the column names
587
+ # each item clicked yields the block with the selected iterator, double-click also close the popup
588
+ def initialize(owner, title, list, h={})
589
+ owner ||= Gtk::Window.toplevels.first
590
+ super(title, owner, Gtk::Dialog::DESTROY_WITH_PARENT)
591
+
592
+ cols = list.shift
593
+
594
+ treeview = Gtk::TreeView.new
595
+ treeview.model = Gtk::ListStore.new(*[String]*cols.length)
596
+ treeview.selection.mode = Gtk::SELECTION_NONE
597
+ if @color_callback = h[:color_callback]
598
+ @drawable = DrawableWidget.new # needed for color()...
599
+ @drawable.signal_emit('realize')
600
+ end
601
+
602
+ cols.each_with_index { |col, i|
603
+ crt = Gtk::CellRendererText.new
604
+ tvc = Gtk::TreeViewColumn.new(col, crt)
605
+ tvc.sort_column_id = i
606
+ tvc.set_cell_data_func(crt) { |_tvc, _crt, model, iter|
607
+ _crt.text = iter[i]
608
+ if @color_callback
609
+ fu = (0...cols.length).map { |ii| iter[ii] }
610
+ fg, bg = @color_callback[fu]
611
+ fg ||= :black
612
+ bg ||= :white
613
+ _crt.foreground = @drawable.color(fg).to_s
614
+ _crt.cell_background = @drawable.color(bg).to_s
615
+ end
616
+ }
617
+ treeview.append_column tvc
618
+ }
619
+
620
+ list.each { |e|
621
+ iter = treeview.model.append
622
+ e.each_with_index { |v, i| iter[i] = v.to_s }
623
+ }
624
+
625
+ treeview.model.set_sort_column_id(0)
626
+
627
+ treeview.signal_connect('cursor_changed') { |x|
628
+ if iter = treeview.selection.selected
629
+ yield iter
630
+ end
631
+ }
632
+ treeview.signal_connect('row_activated') { destroy }
633
+
634
+ signal_connect('destroy') { h[:ondestroy].call } if h[:ondestroy]
635
+
636
+ remove vbox
637
+ add Gtk::ScrolledWindow.new.add(treeview)
638
+ toplevel.set_default_size cols.length*120, 400
639
+
640
+ show if not h[:noshow]
641
+
642
+ # so that the 1st line is not selected by default
643
+ treeview.selection.mode = Gtk::SELECTION_SINGLE
644
+ end
645
+
646
+ def show
647
+ show_all
648
+ present
649
+ end
650
+ end
651
+
652
+ class Window < Gtk::Window
653
+ include WindowPos
654
+ include Msgbox
655
+
656
+ attr_accessor :menu
657
+ def initialize(*a, &b)
658
+ super()
659
+
660
+ signal_connect('destroy') { destroy_window }
661
+
662
+ @vbox = Gtk::VBox.new
663
+ add @vbox
664
+
665
+ @menu = []
666
+ @menubar = Gtk::MenuBar.new
667
+ @accel_group = Gtk::AccelGroup.new
668
+
669
+ @vbox.add @menubar, 'expand' => false
670
+ @child = nil
671
+ s = Gdk::Screen.default
672
+ set_default_size s.width*3/4, s.height*3/4
673
+
674
+ Gtk::Settings.default.gtk_menu_bar_accel = nil # disable F10 -> focus menubar
675
+
676
+ (@@mainwindow_list ||= []) << self
677
+
678
+ initialize_window(*a, &b)
679
+ build_menu
680
+ update_menu
681
+
682
+
683
+ Gtk::Drag.dest_set(self,
684
+ Gtk::Drag::DEST_DEFAULT_MOTION |
685
+ Gtk::Drag::DEST_DEFAULT_DROP,
686
+ [['text/plain', 0, 0], ['text/uri-list', 0, 0]],
687
+ Gdk::DragContext::ACTION_COPY | Gdk::DragContext::ACTION_MOVE)
688
+
689
+ signal_connect('drag_data_received') { |w, dc, x, y, data, info, time|
690
+ dc.targets.each { |target|
691
+ next if target.name != 'text/plain' and target.name != 'text/uri-list'
692
+ data.data.each_line { |l|
693
+ next if not @child or not @child.respond_to? :dragdropfile
694
+ l = l.chomp.sub(%r{^file://}, '')
695
+ protect { @child.dragdropfile(l) }
696
+ }
697
+ }
698
+ Gtk::Drag.finish(dc, true, false, time)
699
+ }
700
+
701
+ show_all
702
+ end
703
+
704
+ def destroy_window
705
+ @@mainwindow_list.delete self
706
+ Gui.main_quit if @@mainwindow_list.empty? # XXX we don't call main_start ourself..
707
+ end
708
+
709
+ def widget=(w)
710
+ @vbox.remove @child if @child
711
+ @child = w
712
+ @vbox.add w if w
713
+ show_all
714
+ end
715
+
716
+ def widget
717
+ @child
718
+ end
719
+
720
+ def build_menu
721
+ end
722
+
723
+ def new_menu
724
+ []
725
+ end
726
+
727
+ # finds a menu by name (recursive)
728
+ # returns a valid arg for addsubmenu(ret)
729
+ def find_menu(name, from=@menu)
730
+ name = name.gsub('_', '')
731
+ if not l = from.find { |e| e.grep(::String).find { |es| es.gsub('_', '') == name } }
732
+ l = from.map { |e| e.grep(::Array).map { |ae| find_menu(name, ae) }.compact.first }.compact.first
733
+ end
734
+ l.grep(::Array).first if l
735
+ end
736
+
737
+ # append stuff to a menu
738
+ # arglist:
739
+ # empty = menu separator
740
+ # string = menu entry display name (use a single '_' keyboard for shortcut, eg 'Sho_rtcut' => 'r')
741
+ # :check = menu entry is a checkbox type, add a true/false argument to specify initial value
742
+ # second string = keyboard shortcut (accelerator) - use '^' for Ctrl, and '<up>' for special keys
743
+ # a menu object = this entry will open a submenu (you must specify a name, and action is ignored)
744
+ # the method takes a block or a Proc argument that will be run whenever the menu item is selected
745
+ #
746
+ # use @menu to reference the top-level menu bar
747
+ # call update_menu when the menu is done
748
+ def addsubmenu(menu, *args, &action)
749
+ args << action if action
750
+ menu << args
751
+ menu.last
752
+ end
753
+
754
+ # make the window's MenuBar reflect the content of @menu
755
+ def update_menu
756
+ # clear
757
+ @menubar.children.dup.each { |mc| @menubar.remove mc }
758
+ # populate the menubar using @menu
759
+ @menu.each { |e| create_menu_item(@menubar, e) }
760
+ @menubar.show_all
761
+ end
762
+
763
+ def create_menu_item(menu, entry)
764
+ args = entry.dup
765
+
766
+ # recognise 'OPEN', 'SAVE' etc, with special icon/localisation
767
+ stock = (Gtk::Stock.constants.map { |c| c.to_s } & args).first
768
+ args.delete stock if stock
769
+ accel = args.grep(/^\^?(\w|<\w+>)$/).first
770
+ args.delete accel if accel
771
+ check = args.delete :check
772
+ action = args.grep(::Proc).first
773
+ args.delete action if action
774
+ if submenu = args.grep(::Array).first
775
+ args.delete submenu
776
+ sm = Gtk::Menu.new
777
+ submenu.each { |e| create_menu_item(sm, e) }
778
+ submenu = sm
779
+ end
780
+ label = args.shift
781
+
782
+ if stock
783
+ item = Gtk::ImageMenuItem.new(Gtk::Stock.const_get(stock))
784
+ begin
785
+ item.label = label if label
786
+ rescue
787
+ # in some version of gtk, no #label=
788
+ item = Gtk::MenuItem.new(label) if label
789
+ end
790
+ elsif check
791
+ item = Gtk::CheckMenuItem.new(label)
792
+ item.active = args.shift
793
+ elsif label
794
+ item = Gtk::MenuItem.new(label)
795
+ else
796
+ item = Gtk::MenuItem.new
797
+ end
798
+ item.set_submenu(submenu) if submenu
799
+
800
+ if accel
801
+ key = accel[-1]
802
+ if key == ?>
803
+ key = accel[/<(.*)>/, 1]
804
+ key = case key
805
+ when 'enter'; Gdk::Keyval::GDK_Return
806
+ when 'esc'; Gdk::Keyval::GDK_Escape
807
+ when 'tab'; Gdk::Keyval::GDK_Tab
808
+ when /^f(\d\d?)$/i; Gdk::Keyval.const_get("GDK_#{key.upcase}")
809
+ else ??
810
+ end
811
+ end
812
+ key = key.unpack('C')[0] if key.kind_of? String # yay rb19
813
+ item.add_accelerator('activate', @accel_group, key, (accel[0] == ?^ ? Gdk::Window::CONTROL_MASK : 0), Gtk::ACCEL_VISIBLE)
814
+ end
815
+ if action
816
+ a = action
817
+ if check
818
+ a = lambda { item.active = action.call(item.active?) }
819
+ end
820
+ item.signal_connect('activate') { protect { a.call(item) } }
821
+ end
822
+ menu.append item
823
+ item
824
+ end
825
+
826
+ def initialize_window
827
+ end
828
+ end
829
+
830
+ class ToolWindow < Gtk::Dialog
831
+ include WindowPos
832
+
833
+ def initialize(parent=nil, *a, &b)
834
+ super('toolwin', parent, Gtk::Dialog::DESTROY_WITH_PARENT)
835
+ set_events Gdk::Event::ALL_EVENTS_MASK
836
+ set_can_focus true
837
+ @child = vbox
838
+ initialize_window(*a, &b)
839
+ show_all
840
+ end
841
+
842
+ def widget=(w)
843
+ remove @child if @child
844
+ @child = w
845
+ add @child
846
+ if @child.respond_to? :initial_size
847
+ resize(*@child.initial_size)
848
+ end
849
+ show_all
850
+ end
851
+
852
+ def widget
853
+ @child
854
+ end
855
+ end
856
+
857
+ # start the Gui main loop
858
+ def self.main
859
+ Gtk.main
860
+ end
861
+
862
+ # ends the Gui main loop
863
+ def self.main_quit
864
+ Gtk.main_quit
865
+ end
866
+
867
+ # register a proc to be run whenever the gui loop is idle
868
+ # if the proc returns nil/false, delete it
869
+ def self.idle_add(&b)
870
+ Gtk.idle_add(&b)
871
+ end
872
+
873
+ # run a single iteration of the main_loop
874
+ # e.g. call this from time to time when doing heavy computation, to keep the UI somewhat responsive
875
+ def self.main_iter
876
+ Gtk.main_iteration_do(false)
877
+ end
878
+
879
+ end
880
+ end
881
+
882
+ require 'metasm/gui/dasm_main'
883
+ require 'metasm/gui/debug'
884
+