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.
- 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
|