metasm 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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