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,49 @@
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
+ # shows the compilation phase step by step: c, simplified c, asm
7
+
8
+ require 'metasm'
9
+ require 'optparse'
10
+
11
+ opts = { :cpu => 'Ia32', :exe => 'Shellcode', :macros => {} }
12
+ OptionParser.new { |opt|
13
+ opt.on('--pic', 'generate position-independant code') { opts[:pic] = true }
14
+ opt.on('--cpu cpu') { |c| opts[:cpu] = c }
15
+ opt.on('--exe exe') { |e| opts[:exe] = e }
16
+ opt.on('-D var=val', 'define a preprocessor macro') { |v| v0, v1 = v.split('=', 2) ; opts[:macros][v0] = v1 }
17
+ opt.on('-v') { $VERBOSE = true }
18
+ opt.on('-d') { $VERBOSE = $DEBUG = true }
19
+ opt.on('-e src') { |s| opts[:src] = s }
20
+ }.parse!(ARGV)
21
+
22
+ src = opts[:src] || (ARGV.empty? ? <<EOS : ARGF.read)
23
+ void foo(int);
24
+ void bla()
25
+ {
26
+ int i = 10;
27
+ while (--i)
28
+ foo(i);
29
+ }
30
+ EOS
31
+
32
+ pp = opts[:macros].map { |k, v| "#define #{k} #{v}" }.join("\n")
33
+
34
+ cpu = Metasm.const_get(opts[:cpu]).new
35
+ exe = Metasm.const_get(opts[:exe]).new(cpu)
36
+ cpu.generate_PIC = false unless opts[:pic]
37
+
38
+ cp = Metasm::C::Parser.new(exe)
39
+ cp.parse pp
40
+ cp.parse src
41
+ puts cp, '', ' ----', ''
42
+
43
+ cp.precompile
44
+ puts cp, '', ' ----', ''
45
+
46
+ cp = Metasm::C::Parser.new(exe)
47
+ cp.parse pp
48
+ cp.parse src
49
+ puts cpu.new_ccompiler(cp).compile
@@ -0,0 +1,55 @@
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
+ #
8
+ # This script takes a C header or a path to a Visual Studio install and
9
+ # outputs a ruby source file defining StackOffsets, a hash used by the disassembler
10
+ # In verbose mode (ruby -v), instead dumps the parsed header (+ warnings)
11
+ #
12
+
13
+ require 'metasm'
14
+
15
+ filename = ARGV.shift
16
+ abort "usage: #$0 filename" if not File.exist? filename
17
+
18
+ # path to visual studio install directory
19
+ if File.directory? filename
20
+ src = <<EOS
21
+ // add the path to the visual studio std headers
22
+ #ifdef __METASM__
23
+ #pragma include_dir #{(filename+'/VC/platformsdk/include').inspect}
24
+ #pragma include_dir #{(filename+'/VC/include').inspect}
25
+ #pragma prepare_visualstudio
26
+ #pragma no_warn_redefinition
27
+ #endif
28
+
29
+ #define WIN32_LEAN_AND_MEAN
30
+ #include <windows.h>
31
+ EOS
32
+ else
33
+ # standalone header
34
+ src = File.read(filename)
35
+ end
36
+
37
+ include Metasm
38
+
39
+ cp = Ia32.new.new_cparser.parse(src)
40
+
41
+ if not $VERBOSE
42
+ funcs = cp.toplevel.symbol.values.grep(C::Variable).reject { |v| v.initializer or not v.type.kind_of? C::Function }
43
+
44
+ puts 'module Metasm'
45
+ puts 'StackOffsets = {'
46
+ align = lambda { |val| (val + cp.typesize[:ptr] - 1) / cp.typesize[:ptr] * cp.typesize[:ptr] }
47
+ puts funcs.find_all { |f| f.attributes and f.attributes.include? 'stdcall' and f.type.args }.sort_by { |f| f.name }.map { |f|
48
+ "#{f.name.inspect} => #{f.type.args.inject(0) { |sum, arg| sum + align[cp.sizeof(arg)] }}"
49
+ }.join(",\n")
50
+ puts '}'
51
+ puts 'end'
52
+ else
53
+ # dump the full parsed header
54
+ puts cp.lexer.dump_macros(cp.lexer.definition.keys, false), '', '', cp
55
+ end
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ # This file is part of Metasm, the Ruby assembly manipulation suite
3
+ # Copyright (C) 2006-2009 Yoann GUILLOT
4
+ #
5
+ # Licence is LGPL, see LICENCE in the top-level directory
6
+
7
+
8
+ #
9
+ # quick demonstration that the disassembler's backtracker works
10
+ #
11
+
12
+ require 'metasm'
13
+ Metasm.require 'samples/metasm-shell'
14
+
15
+ puts <<EOS.encode.decode
16
+ .base_addr 0
17
+
18
+ ; compute jump target
19
+ mov ebx, 0x12345678
20
+ mov eax, ((toto + 12) ^ 0x12345678)
21
+ xor eax, ebx
22
+ sub eax, 12
23
+
24
+ ; jump
25
+ call eax
26
+
27
+ ; trap
28
+ add eax, 42
29
+ ; die, you vile reverser !
30
+ db 0e9h
31
+
32
+ ; real target
33
+ toto:
34
+ mov eax, 28h
35
+ pop ebx
36
+ ret
37
+
38
+ EOS
@@ -0,0 +1,318 @@
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
+ # this is a little script to navigate in a disassembler dump
8
+ #
9
+
10
+ # copypasted from lindebug.rb
11
+ module Ansi
12
+ CursHome = "\e[H".freeze
13
+ ClearLineAfter = "\e[0K"
14
+ ClearLineBefore = "\e[1K"
15
+ ClearLine = "\e[2K"
16
+ ClearScreen = "\e[2J"
17
+ def self.set_cursor_pos(y=1,x=1) "\e[#{y};#{x}H" end
18
+ Reset = "\e[m"
19
+ Colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white, :aoeu, :reset]
20
+ def self.color(*args)
21
+ fg = true
22
+ "\e[" << args.map { |a|
23
+ case a
24
+ when :bold; 2
25
+ when :negative; 7
26
+ when :normal; 22
27
+ when :positive; 27
28
+ else
29
+ if col = Colors.index(a)
30
+ add = (fg ? 30 : 40)
31
+ fg = false
32
+ col+add
33
+ end
34
+ end
35
+ }.compact.join(';') << 'm'
36
+ end
37
+ def self.hline(len) "\e(0"<<'q'*len<<"\e(B" end
38
+
39
+ TIOCGWINSZ = 0x5413
40
+ TCGETS = 0x5401
41
+ TCSETS = 0x5402
42
+ CANON = 2
43
+ ECHO = 8
44
+ def self.get_terminal_size
45
+ s = ''.ljust(8)
46
+ $stdin.ioctl(TIOCGWINSZ, s) >= 0 ? s.unpack('SS') : [80, 25]
47
+ end
48
+ def self.set_term_canon(bool)
49
+ tty = ''.ljust(256)
50
+ $stdin.ioctl(TCGETS, tty)
51
+ if bool
52
+ tty[12] &= ~(ECHO|CANON)
53
+ else
54
+ tty[12] |= ECHO|CANON
55
+ end
56
+ $stdin.ioctl(TCSETS, tty)
57
+ end
58
+
59
+ ESC_SEQ = {'A' => :up, 'B' => :down, 'C' => :right, 'D' => :left,
60
+ '1~' => :home, '2~' => :inser, '3~' => :suppr, '4~' => :end,
61
+ '5~' => :pgup, '6~' => :pgdown,
62
+ 'P' => :f1, 'Q' => :f2, 'R' => :f3, 'S' => :f4,
63
+ '15~' => :f5, '17~' => :f6, '18~' => :f7, '19~' => :f8,
64
+ '20~' => :f9, '21~' => :f10, '23~' => :f11, '24~' => :f12,
65
+ '[A' => :f1, '[B' => :f2, '[C' => :f3, '[D' => :f4, '[E' => :f5,
66
+ 'H' => :home, 'F' => :end,
67
+ }
68
+ def self.getkey
69
+ c = $stdin.getc
70
+ return c if c != ?\e
71
+ c = $stdin.getc
72
+ if c != ?[ and c != ?O
73
+ $stdin.ungetc c
74
+ return ?\e
75
+ end
76
+ seq = ''
77
+ loop do
78
+ c = $stdin.getc
79
+ seq << c
80
+ case c; when ?a..?z, ?A..?Z, ?~; break end
81
+ end
82
+ ESC_SEQ[seq] || seq
83
+ end
84
+ end
85
+
86
+ class Viewer
87
+ attr_accessor :text, :pos, :x, :y
88
+
89
+ Color = {
90
+ :normal => Ansi.color(:white, :black, :normal),
91
+ :comment => Ansi.color(:blue),
92
+ :label => Ansi.color(:green),
93
+ :hilight => Ansi.color(:yellow),
94
+ }
95
+
96
+
97
+ def initialize(text)
98
+ text = File.read(text) if File.exist? text rescue nil
99
+ @text = text.gsub("\t", " "*8).to_a.map { |l| l.chomp }
100
+ @pos = @posh = 0
101
+ @x = @y = 0
102
+ @mode = :navig
103
+ @searchtext = 'x'
104
+ @posstack = []
105
+ @h, @w = Ansi.get_terminal_size
106
+ @h -= 2
107
+ @w -= 1
108
+ if y = @text.index('entrypoint:')
109
+ view(0, y)
110
+ end
111
+ end
112
+
113
+ def main_loop
114
+ Ansi.set_term_canon(true)
115
+ $stdout.write Ansi::ClearScreen
116
+ begin
117
+ loop do
118
+ refresh if not s = IO.select([$stdin], nil, nil, 0)
119
+ handle_key(Ansi.getkey)
120
+ end
121
+ ensure
122
+ Ansi.set_term_canon(false)
123
+ $stdout.write Ansi.set_cursor_pos(@h+2, 0) + Ansi::ClearLineAfter
124
+ end
125
+ end
126
+
127
+ def refresh
128
+ case @mode
129
+ when :navig
130
+ refresh_navig
131
+ when :search
132
+ refresh_search
133
+ end
134
+ end
135
+
136
+ def refresh_navig
137
+ str = ''
138
+ #str << Ansi::ClearScreen
139
+ str << Ansi.set_cursor_pos(0, 0)
140
+ hl = readtext
141
+ (0..@h).each { |h|
142
+ l = @text[@pos+h] || ''
143
+ str << outline(l, hl) << Ansi::ClearLineAfter << "\n"
144
+ }
145
+ str << Ansi.set_cursor_pos(@y+1, @x+1)
146
+ $stdout.write str
147
+ end
148
+
149
+ def refresh_search
150
+ $stdout.write '' << Ansi.set_cursor_pos(@h+2, 1) << '/' << @searchtext << Ansi::ClearLineAfter
151
+ end
152
+
153
+ def outline(l, hl=nil)
154
+ l = l[@posh, @w] || ''
155
+ hlr = /\b#{Regexp.escape(hl)}\b/i if hl
156
+ case l
157
+ when /^\/\//; Color[:comment] + l + Color[:normal]
158
+ when /^\S+:$/; Color[:label] + l + Color[:normal]
159
+ when /^(.*)(;.*)$/
160
+ str = $1
161
+ cmt = $2
162
+ str.gsub!(hlr, Color[:hilight]+hl+Color[:normal]) if hl
163
+ str + Color[:comment] + cmt + Color[:normal]
164
+ else
165
+ l = l.gsub(hlr, Color[:hilight]+hl+Color[:normal]) if hl
166
+ l
167
+ end
168
+ end
169
+
170
+ def search_prev
171
+ return if @searchtext == ''
172
+ y = @pos+@y-1
173
+ loop do
174
+ y = @text.length-1 if not @text[y] or y < 0
175
+ if x = (@text[y] =~ /#@searchtext/i)
176
+ view(x, y)
177
+ return
178
+ end
179
+ y -= 1
180
+ break if y == @pos+@y
181
+ end
182
+ end
183
+
184
+ def search_next
185
+ return if @searchtext == ''
186
+ y = @pos+@y+1
187
+ loop do
188
+ y = 0 if not @text[y]
189
+ if x = (@text[y] =~ /#@searchtext/i)
190
+ view(x, y)
191
+ return
192
+ end
193
+ break if y == @pos+@y or (y >= @text.length and not @text[@pos+@y])
194
+ y += 1
195
+ end
196
+ end
197
+
198
+ def view(x, y)
199
+ @posh, @x = 0, x
200
+ if @x > @w
201
+ @posh = @w-@x
202
+ @x = @w
203
+ end
204
+ if @pos+@h < y
205
+ @y = @h/2-1
206
+ @pos = y-@y
207
+ elsif @pos > y
208
+ @y = 1
209
+ @pos = y-@y
210
+ else
211
+ @y = y-@pos
212
+ end
213
+ end
214
+
215
+ def readtext
216
+ return if not l = @text[@pos+@y]
217
+ x = (l.rindex(/\W/, [@posh+@x-1, 0].max) || -1)+1
218
+ t = l[x..-1][/^\w+/]
219
+ t if t and @posh+@x < x+t.length
220
+ end
221
+
222
+ def handle_key(k)
223
+ case @mode
224
+ when :navig
225
+ handle_key_navig(k)
226
+ when :search
227
+ handle_key_search(k)
228
+ end
229
+ end
230
+
231
+ def handle_key_search(k)
232
+ case k
233
+ when ?\n; @mode = :navig ; @posstack << [@posh, @pos, @x, @y] ; search_next
234
+ when 0x20..0x7e; @searchtext << k
235
+ when :backspace, 0x7f; @searchtext.chop!
236
+ end
237
+ end
238
+
239
+ def handle_key_navig(k)
240
+ case k
241
+ when :f1
242
+ if not @posstack.empty?
243
+ @posh, @pos, @x, @y = @posstack.pop
244
+ end
245
+ when ?\n
246
+ return if not label = readtext
247
+ return if label.empty? or not newy = @text.index(@text.find { |l| l[0, label.length] == label }) or newy == @pos+@y
248
+ @posstack << [@posh, @pos, @x, @y]
249
+ view(0, newy)
250
+ when :up
251
+ if @y > 0; @y -= 1
252
+ elsif @pos > 0; @pos -= 1
253
+ end
254
+ when :down
255
+ if @y < @h; @y += 1
256
+ elsif @pos < text.length-@h; @pos += 1
257
+ end
258
+ when :home
259
+ @x = @posh = 0
260
+ when :end
261
+ @x = @text[@pos+@y].length
262
+ @posh, @x = @x-@w, @w if @x > @w
263
+ when :left
264
+ x = @text[@pos+@y].rindex(/\W\w/, [@posh+@x-2, 0].max)
265
+ x = x ? x+1 : @posh+@x-1
266
+ x = @posh+@x-3 if x < @posh+@x-3
267
+ x = 0 if x < 0
268
+ if x < @posh; @posh, @x = x, 0
269
+ else @x = x-@posh
270
+ end
271
+ #if @x > 0; @x -= 1
272
+ #elsif @posh > 0; @posh -= 1
273
+ #end
274
+ when :right
275
+ x = @text[@pos+@y].index(/\W\w/, @posh+@x)
276
+ x = x ? x+1 : @posh+@x+1
277
+ x = @posh+@x+3 if x > @posh+@x+3
278
+ if x > @posh+@w; @posh, @x = x-@w, @w
279
+ else
280
+ @x = x-@posh
281
+ @posh, @x = @x-@w, @w if @x > @w
282
+ end
283
+ #if @x < @w; @x += 1
284
+ #elsif @posh+@w < (@text[@pos, @h].map { |l| l.length }.max); @posh += 1
285
+ #end
286
+ when :pgdown
287
+ if @y < @h/2; @y += @h/2
288
+ elsif @pos < @text.length-3*@h/2; @pos += @h/2 ; @y = @h
289
+ else @pos = [0, @text.length-@h].max ; @y = @h
290
+ end
291
+ when :pgup
292
+ if @y > @h/2; @y -= @h/2
293
+ elsif @pos > @h/2; @pos -= @h/2 ; @y = 0
294
+ else @pos = @y = 0
295
+ end
296
+ when ?q; exit
297
+ when ?o; @text.insert(@pos+@y+1, '')
298
+ when ?O; @text.insert(@pos+@y, '') ; handle_key_navig(:down)
299
+ when :suppr; @text.delete_at(@pos+@y) if @text[@pos+@y] == ''
300
+ when ?D; @text.delete_at(@pos+@y)
301
+ when ?/
302
+ @mode = :search
303
+ @searchtext = ''
304
+ when ?*
305
+ @searchtext = readtext || ''
306
+ search_next
307
+ when ?n; search_next
308
+ when ?N; search_prev
309
+ when :f5
310
+ ARGV << '--reload'
311
+ load $0
312
+ end
313
+ end
314
+ end
315
+
316
+ if $0 == __FILE__ and not ARGV.delete '--reload'
317
+ Viewer.new(ARGF.read).main_loop
318
+ end