metasm 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/BUGS +11 -0
- data/CREDITS +17 -0
- data/README +270 -0
- data/TODO +114 -0
- data/doc/code_organisation.txt +146 -0
- data/doc/const_missing.txt +16 -0
- data/doc/core_classes.txt +75 -0
- data/doc/feature_list.txt +53 -0
- data/doc/index.txt +59 -0
- data/doc/install_notes.txt +170 -0
- data/doc/style.css +3 -0
- data/doc/use_cases.txt +18 -0
- data/lib/metasm.rb +80 -0
- data/lib/metasm/arm.rb +12 -0
- data/lib/metasm/arm/debug.rb +39 -0
- data/lib/metasm/arm/decode.rb +167 -0
- data/lib/metasm/arm/encode.rb +77 -0
- data/lib/metasm/arm/main.rb +75 -0
- data/lib/metasm/arm/opcodes.rb +177 -0
- data/lib/metasm/arm/parse.rb +130 -0
- data/lib/metasm/arm/render.rb +55 -0
- data/lib/metasm/compile_c.rb +1457 -0
- data/lib/metasm/dalvik.rb +8 -0
- data/lib/metasm/dalvik/decode.rb +196 -0
- data/lib/metasm/dalvik/main.rb +60 -0
- data/lib/metasm/dalvik/opcodes.rb +366 -0
- data/lib/metasm/decode.rb +213 -0
- data/lib/metasm/decompile.rb +2659 -0
- data/lib/metasm/disassemble.rb +2068 -0
- data/lib/metasm/disassemble_api.rb +1280 -0
- data/lib/metasm/dynldr.rb +1329 -0
- data/lib/metasm/encode.rb +333 -0
- data/lib/metasm/exe_format/a_out.rb +194 -0
- data/lib/metasm/exe_format/autoexe.rb +82 -0
- data/lib/metasm/exe_format/bflt.rb +189 -0
- data/lib/metasm/exe_format/coff.rb +455 -0
- data/lib/metasm/exe_format/coff_decode.rb +901 -0
- data/lib/metasm/exe_format/coff_encode.rb +1078 -0
- data/lib/metasm/exe_format/dex.rb +457 -0
- data/lib/metasm/exe_format/dol.rb +145 -0
- data/lib/metasm/exe_format/elf.rb +923 -0
- data/lib/metasm/exe_format/elf_decode.rb +979 -0
- data/lib/metasm/exe_format/elf_encode.rb +1375 -0
- data/lib/metasm/exe_format/macho.rb +827 -0
- data/lib/metasm/exe_format/main.rb +228 -0
- data/lib/metasm/exe_format/mz.rb +164 -0
- data/lib/metasm/exe_format/nds.rb +172 -0
- data/lib/metasm/exe_format/pe.rb +437 -0
- data/lib/metasm/exe_format/serialstruct.rb +246 -0
- data/lib/metasm/exe_format/shellcode.rb +114 -0
- data/lib/metasm/exe_format/xcoff.rb +167 -0
- data/lib/metasm/gui.rb +23 -0
- data/lib/metasm/gui/cstruct.rb +373 -0
- data/lib/metasm/gui/dasm_coverage.rb +199 -0
- data/lib/metasm/gui/dasm_decomp.rb +369 -0
- data/lib/metasm/gui/dasm_funcgraph.rb +103 -0
- data/lib/metasm/gui/dasm_graph.rb +1354 -0
- data/lib/metasm/gui/dasm_hex.rb +543 -0
- data/lib/metasm/gui/dasm_listing.rb +599 -0
- data/lib/metasm/gui/dasm_main.rb +906 -0
- data/lib/metasm/gui/dasm_opcodes.rb +291 -0
- data/lib/metasm/gui/debug.rb +1228 -0
- data/lib/metasm/gui/gtk.rb +884 -0
- data/lib/metasm/gui/qt.rb +495 -0
- data/lib/metasm/gui/win32.rb +3004 -0
- data/lib/metasm/gui/x11.rb +621 -0
- data/lib/metasm/ia32.rb +14 -0
- data/lib/metasm/ia32/compile_c.rb +1523 -0
- data/lib/metasm/ia32/debug.rb +193 -0
- data/lib/metasm/ia32/decode.rb +1167 -0
- data/lib/metasm/ia32/decompile.rb +564 -0
- data/lib/metasm/ia32/encode.rb +314 -0
- data/lib/metasm/ia32/main.rb +233 -0
- data/lib/metasm/ia32/opcodes.rb +872 -0
- data/lib/metasm/ia32/parse.rb +327 -0
- data/lib/metasm/ia32/render.rb +91 -0
- data/lib/metasm/main.rb +1193 -0
- data/lib/metasm/mips.rb +11 -0
- data/lib/metasm/mips/compile_c.rb +7 -0
- data/lib/metasm/mips/decode.rb +253 -0
- data/lib/metasm/mips/encode.rb +51 -0
- data/lib/metasm/mips/main.rb +72 -0
- data/lib/metasm/mips/opcodes.rb +443 -0
- data/lib/metasm/mips/parse.rb +51 -0
- data/lib/metasm/mips/render.rb +43 -0
- data/lib/metasm/os/gnu_exports.rb +270 -0
- data/lib/metasm/os/linux.rb +1112 -0
- data/lib/metasm/os/main.rb +1686 -0
- data/lib/metasm/os/remote.rb +527 -0
- data/lib/metasm/os/windows.rb +2027 -0
- data/lib/metasm/os/windows_exports.rb +745 -0
- data/lib/metasm/parse.rb +876 -0
- data/lib/metasm/parse_c.rb +3938 -0
- data/lib/metasm/pic16c/decode.rb +42 -0
- data/lib/metasm/pic16c/main.rb +17 -0
- data/lib/metasm/pic16c/opcodes.rb +68 -0
- data/lib/metasm/ppc.rb +11 -0
- data/lib/metasm/ppc/decode.rb +264 -0
- data/lib/metasm/ppc/decompile.rb +251 -0
- data/lib/metasm/ppc/encode.rb +51 -0
- data/lib/metasm/ppc/main.rb +129 -0
- data/lib/metasm/ppc/opcodes.rb +410 -0
- data/lib/metasm/ppc/parse.rb +52 -0
- data/lib/metasm/preprocessor.rb +1277 -0
- data/lib/metasm/render.rb +130 -0
- data/lib/metasm/sh4.rb +8 -0
- data/lib/metasm/sh4/decode.rb +336 -0
- data/lib/metasm/sh4/main.rb +292 -0
- data/lib/metasm/sh4/opcodes.rb +381 -0
- data/lib/metasm/x86_64.rb +12 -0
- data/lib/metasm/x86_64/compile_c.rb +1025 -0
- data/lib/metasm/x86_64/debug.rb +59 -0
- data/lib/metasm/x86_64/decode.rb +268 -0
- data/lib/metasm/x86_64/encode.rb +264 -0
- data/lib/metasm/x86_64/main.rb +135 -0
- data/lib/metasm/x86_64/opcodes.rb +118 -0
- data/lib/metasm/x86_64/parse.rb +68 -0
- data/misc/bottleneck.rb +61 -0
- data/misc/cheader-findpppath.rb +58 -0
- data/misc/hexdiff.rb +74 -0
- data/misc/hexdump.rb +55 -0
- data/misc/metasm-all.rb +13 -0
- data/misc/objdiff.rb +47 -0
- data/misc/objscan.rb +40 -0
- data/misc/pdfparse.rb +661 -0
- data/misc/ppc_pdf2oplist.rb +192 -0
- data/misc/tcp_proxy_hex.rb +84 -0
- data/misc/txt2html.rb +440 -0
- data/samples/a.out.rb +31 -0
- data/samples/asmsyntax.rb +77 -0
- data/samples/bindiff.rb +555 -0
- data/samples/compilation-steps.rb +49 -0
- data/samples/cparser_makestackoffset.rb +55 -0
- data/samples/dasm-backtrack.rb +38 -0
- data/samples/dasmnavig.rb +318 -0
- data/samples/dbg-apihook.rb +228 -0
- data/samples/dbghelp.rb +143 -0
- data/samples/disassemble-gui.rb +102 -0
- data/samples/disassemble.rb +133 -0
- data/samples/dump_upx.rb +95 -0
- data/samples/dynamic_ruby.rb +1929 -0
- data/samples/elf_list_needed.rb +46 -0
- data/samples/elf_listexports.rb +33 -0
- data/samples/elfencode.rb +25 -0
- data/samples/exeencode.rb +128 -0
- data/samples/factorize-headers-elfimports.rb +77 -0
- data/samples/factorize-headers-peimports.rb +109 -0
- data/samples/factorize-headers.rb +43 -0
- data/samples/gdbclient.rb +583 -0
- data/samples/generate_libsigs.rb +102 -0
- data/samples/hotfix_gtk_dbg.rb +59 -0
- data/samples/install_win_env.rb +78 -0
- data/samples/lindebug.rb +924 -0
- data/samples/linux_injectsyscall.rb +95 -0
- data/samples/machoencode.rb +31 -0
- data/samples/metasm-shell.rb +91 -0
- data/samples/pe-hook.rb +69 -0
- data/samples/pe-ia32-cpuid.rb +203 -0
- data/samples/pe-mips.rb +35 -0
- data/samples/pe-shutdown.rb +78 -0
- data/samples/pe-testrelocs.rb +51 -0
- data/samples/pe-testrsrc.rb +24 -0
- data/samples/pe_listexports.rb +31 -0
- data/samples/peencode.rb +19 -0
- data/samples/peldr.rb +494 -0
- data/samples/preprocess-flatten.rb +19 -0
- data/samples/r0trace.rb +308 -0
- data/samples/rubstop.rb +399 -0
- data/samples/scan_pt_gnu_stack.rb +54 -0
- data/samples/scanpeexports.rb +62 -0
- data/samples/shellcode-c.rb +40 -0
- data/samples/shellcode-dynlink.rb +146 -0
- data/samples/source.asm +34 -0
- data/samples/struct_offset.rb +47 -0
- data/samples/testpe.rb +32 -0
- data/samples/testraw.rb +45 -0
- data/samples/win32genloader.rb +132 -0
- data/samples/win32hooker-advanced.rb +169 -0
- data/samples/win32hooker.rb +96 -0
- data/samples/win32livedasm.rb +33 -0
- data/samples/win32remotescan.rb +133 -0
- data/samples/wintrace.rb +92 -0
- data/tests/all.rb +8 -0
- data/tests/dasm.rb +39 -0
- data/tests/dynldr.rb +35 -0
- data/tests/encodeddata.rb +132 -0
- data/tests/ia32.rb +82 -0
- data/tests/mips.rb +116 -0
- data/tests/parse_c.rb +239 -0
- data/tests/preprocessor.rb +269 -0
- data/tests/x86_64.rb +62 -0
- 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
|