resedit 1.3.1

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.
@@ -0,0 +1,166 @@
1
+ require 'resedit/mz/mz_header'
2
+ require 'resedit/mz/mz_body'
3
+ require 'resedit/mz/hexwriter'
4
+ require 'resedit/mz/mzenv'
5
+
6
+ module Resedit
7
+
8
+ class MZ
9
+ ZM = 0x4D5A
10
+
11
+ attr_reader :fname, :path, :name, :fsize
12
+ attr_reader :header, :body
13
+
14
+ def initialize(path, quiet = false)
15
+ raise "File not specified" if !path
16
+ @quiet = quiet
17
+ @path = path.downcase()
18
+ @fsize = File.size(path)
19
+ open(@path,"rb:ascii-8bit"){|f|
20
+ @header = MZHeader.new(self, f, fsize)
21
+ hsz = @header.headerSize()
22
+ @body = MZBody.new(self, f, @header.fileSize() - hsz)
23
+ save = f.read(2)
24
+ zm = save ? save.unpack('v')[0] : nil
25
+ if zm == ZM
26
+ @header.loadChanges(f)
27
+ @body.loadChanges(f)
28
+ log("Change info loaded.")
29
+ end
30
+ }
31
+ @fname = File.basename(@path)
32
+ @name = File.basename(@path, ".*")
33
+ hi = @header.info()
34
+ env().set(:entry, hi[:CS].to_s+":"+hi[:IP].to_s)
35
+ env().set(:append, sprintf("%04X:0",@body.appSeg))
36
+ end
37
+
38
+
39
+ def close()
40
+ end
41
+
42
+
43
+ def log(fmt, *args)
44
+ App::get().log(fmt, *args) if !@quiet
45
+ end
46
+
47
+
48
+ def env() return MZEnv.instance() end
49
+ def s2i(str) return MZEnv.instance().s2i(str) end
50
+
51
+
52
+ def is?(id)
53
+ id = id.downcase
54
+ return id == @path || id == @fname || id == @name
55
+ end
56
+
57
+
58
+ def print(what, how=nil)
59
+ puts "Header changes:" if what=="changes"
60
+ res = @header.print(what, how)
61
+ puts "Code changes:" if what=="changes"
62
+ res |= @body.print(what, how)
63
+ raise "Don't know how to print: " + what if !res
64
+ end
65
+
66
+
67
+ def hex(ofs, size=nil, how=nil, disp=nil)
68
+ ofs = ofs ? s2i(ofs) : 0
69
+ size = size ? s2i(size) : 0x100
70
+ isfile = disp && (disp[0]=='f' || disp[0]=='F') ? true : false
71
+ wr = HexWriter.new(ofs)
72
+ how = @header.parseHow(how)
73
+ hsz = 0
74
+ if isfile
75
+ @header.mode(how)
76
+ hsz = @header.headerSize()
77
+ size = @header.hex(wr, ofs, size, how) if ofs < hsz
78
+ ofs -= hsz
79
+ ofs = 0 if ofs < 0
80
+ end
81
+ wr.setSegments(@body.segments, hsz)
82
+ @body.hex(wr, ofs, size, how) if size > 0
83
+ wr.finish()
84
+ end
85
+
86
+
87
+ def getValue(value, type)
88
+ s = env().value2bytes(value, type)
89
+ return s.force_encoding(Encoding::ASCII_8BIT)
90
+ end
91
+
92
+
93
+ def append(value, type=nil)
94
+ res = @body.append(getValue(value,type))
95
+ s = ""
96
+ res.each{|a|
97
+ if a.is_a?(Array)
98
+ s += sprintf(" %04X:%04X", a[1], a[0])
99
+ else
100
+ s += sprintf(" %08X", a)
101
+ end
102
+ }
103
+ log("Appended at %s",s)
104
+ end
105
+
106
+
107
+ def replace(value, type=nil)
108
+ @body.removeAppend()
109
+ return append(value,type)
110
+ end
111
+
112
+
113
+ def change(ofs, value, disp=nil, type=nil)
114
+ ofs = s2i(ofs)
115
+ isfile = disp && (disp[0]=='f' || disp[0]=='F') ? true : false
116
+ value = getValue(value, type)
117
+ if isfile
118
+ res = @header.change(ofs,value)
119
+ else
120
+ res = @body.change(ofs,value) + @header.headerSize()
121
+ end
122
+ log("Change added at %08X", res)
123
+ end
124
+
125
+
126
+ def dasm(ofs, size=nil, how=nil)
127
+ ofs = s2i(ofs ? ofs : "entry")
128
+ size = size ? s2i(size) : [0x20, @body.bytes.length-ofs].min
129
+ @body.dasm(ofs, size, how)
130
+ end
131
+
132
+
133
+ def valueof(str, type)
134
+ puts "value of " + str + " is:"
135
+ p getValue(str, type).unpack("H*")
136
+ end
137
+
138
+
139
+ def revert(what)
140
+ wid = env().s2i_nt(what)
141
+ what = wid[1] ? wid[0] : what
142
+ res = @header.revert(what)
143
+ res |= @body.revert(what)
144
+ raise "Don't know how to revert: "+what if !res
145
+ log("Reverted")
146
+ end
147
+
148
+
149
+ def save(filename, final=nil)
150
+ raise "Unknown final: " + final if final && final != "final"
151
+ raise "Filename expected." if !filename
152
+ open(filename, "wb:ascii-8bit"){|f|
153
+ @header.saveData(f)
154
+ @body.saveData(f)
155
+ if !final || final!='final'
156
+ f.write([ZM].pack('v'))
157
+ @header.saveChanges(f)
158
+ @body.saveChanges(f)
159
+ end
160
+ }
161
+ end
162
+
163
+
164
+ end
165
+
166
+ end
@@ -0,0 +1,141 @@
1
+ require 'resedit/mz/changeable'
2
+ begin
3
+ require 'crabstone'
4
+ include Crabstone
5
+ $nocrabstone = false
6
+ rescue LoadError
7
+ $nocrabstone = true
8
+ end
9
+
10
+
11
+ module Resedit
12
+
13
+ class MZBody < Changeable
14
+
15
+ attr_reader :segments, :appSeg
16
+
17
+ def initialize(mz, file, size)
18
+ super(mz, file, size)
19
+ @segments = Set.new()
20
+ for i in 0..@mz.header.info[:NumberOfRelocations]-1
21
+ r = @mz.header.getRelocation(i)
22
+ @segments.add(r[1])
23
+ val = segData(r, 2).unpack('v')[0]
24
+ @segments.add(val)
25
+ end
26
+ @appSeg = (@realSize >> 4) + 1
27
+ end
28
+
29
+
30
+ def loadChanges(f)
31
+ super(f)
32
+ @appSeg = (@realSize >> 4) + 1
33
+ @segments.add(@appSeg)
34
+ end
35
+
36
+
37
+ def seg2Linear(a,s) (s << 4) + a end
38
+
39
+
40
+ def seg4Linear(linear)
41
+ linear >>= 4
42
+ min = @segments.sort.reverse.find{|e| e <= linear}
43
+ return min ? min : 0
44
+ end
45
+
46
+
47
+ def linear2seg(linear, inSegments=nil)
48
+ inSegments = [seg4Linear(linear)] if !inSegments
49
+ res = []
50
+ inSegments.each{|s|
51
+ raise sprintf("Linear %X less than segment %04X", inSegments[0], s) if linear < (s<<4)
52
+ a = linear - (s << 4)
53
+ res += [a,s]
54
+ }
55
+ return res
56
+ end
57
+
58
+
59
+ def segData(reloc, size, isStr=false)
60
+ ofs = seg2Linear(reloc[0], reloc[1])
61
+ return "None" if ofs>@bytes.length
62
+ return getData(ofs, size) if !isStr
63
+ return colVal(ofs, size)
64
+ end
65
+
66
+
67
+ def removeAppend()
68
+ @segments.each{|s|
69
+ @segments.delete(s) if (s << 4) > @realSize
70
+ }
71
+ super()
72
+ end
73
+
74
+
75
+ def revert(what)
76
+ @realOfs = @mz.header.headerSize()
77
+ super(what)
78
+ end
79
+
80
+
81
+ def append(bytes)
82
+ mode(HOW_ORIGINAL)
83
+ res = 0
84
+ addseg = false
85
+ if !@add
86
+ addseg = true
87
+ res = 0x10 - (@realSize % 0x10)
88
+ res = 0 if res == 0x10
89
+ bytes = ("\x90" * res).force_encoding(Encoding::ASCII_8BIT) + bytes if res > 0
90
+ end
91
+ res += super(bytes)
92
+ @mz.header.setCodeSize(@bytes.length + @add.length)
93
+ seg = linear2seg(res)
94
+ res = [res, seg]
95
+ if addseg
96
+ raise "Segs not match" if (@appSeg << 4) != res[0]
97
+ @segments.add(@appSeg)
98
+ res += [ [ 0, @appSeg] ]
99
+ end
100
+ return res
101
+ end
102
+
103
+
104
+ def print(what, how)
105
+ if what=="header"
106
+ puts "Known segments: " + @segments.sort.map{ |i| sprintf('%04X',i) }.join(", ")
107
+ return true
108
+ end
109
+ @realOfs = @mz.header.headerSize()
110
+ return super(what, how)
111
+ end
112
+
113
+
114
+ def dasm(ofs, size, how)
115
+ raise "Crabstone gem required to disasm." if $nocrabstone
116
+ mode(parseHow(how))
117
+ cs = Disassembler.new(ARCH_X86, MODE_16)
118
+ begin
119
+ while true
120
+ begin
121
+ d = getData(ofs,size)
122
+ cs.disasm(d, ofs).each {|i|
123
+ seg = linear2seg(i.address)
124
+ bts = i.bytes.map { |b| sprintf("%02X",b) }.join
125
+ inst = colStr(sprintf("%14s\t%s\t%s", bts, i.mnemonic, i.op_str), changed?(i.address, i.bytes.length))
126
+ printf("%08X %04X:%04X%s\n",i.address, seg[1], seg[0], inst)
127
+ }
128
+ break
129
+ rescue
130
+ ofs-=1
131
+ end
132
+ end
133
+
134
+ ensure
135
+ cs.close()
136
+ end
137
+ end
138
+
139
+
140
+ end
141
+ end
@@ -0,0 +1,123 @@
1
+ require 'resedit/mz/changeable'
2
+
3
+ module Resedit
4
+
5
+ class MZHeader < Changeable
6
+ MAGIC = 0x5a4D
7
+ BLK = 0x200
8
+ PARA = 0x10
9
+ HSIZE = 0x1C
10
+
11
+ attr_reader :info
12
+
13
+ def initialize(mz, file, size)
14
+ raise "Not MZ file" if size < HSIZE
15
+ super(mz, file, HSIZE)
16
+ @fsize = size
17
+ @_infoOrig = loadInfo()
18
+ @_info = nil
19
+ @info = @_infoOrig
20
+ raise "Not MZ file" if MAGIC != @info[:Magic]
21
+ readMore(file, headerSize() - HSIZE)
22
+ end
23
+
24
+
25
+ def mode(how)
26
+ super(how)
27
+ if @mode == HOW_ORIGINAL
28
+ @info = @_infoOrig
29
+ else
30
+ @_info = loadInfo() if !@_info
31
+ @info = @_info
32
+ end
33
+ end
34
+
35
+
36
+ def change(ofs, bytes)
37
+ super(ofs, bytes)
38
+ @_info = nil if (ofs < HSIZE)
39
+ end
40
+
41
+
42
+ def loadInfo()
43
+ v = getData(0, HSIZE).unpack('v*')
44
+ return {:Magic => v[0], :BytesInLastBlock => v[1], :BlocksInFile => v[2], :NumberOfRelocations => v[3],
45
+ :HeaderParagraphs => v[4], :MinExtraParagraphs => v[5], :MaxExtraParagraphs => v[6],
46
+ :SS => v[7], :SP => v[8], :Checksum => v[9], :IP => v[10], :CS => v[11],
47
+ :RelocTableOffset => v[12], :OverlayNumber => v[13]
48
+ }
49
+ end
50
+
51
+
52
+ def setCodeSize(size)
53
+ mode(HOW_CHANGED)
54
+ size += headerSize()
55
+ mod = size % BLK
56
+ ch = [mod, size / BLK + (mod ? 1 : 0)]
57
+ change(2, ch.pack('vv'))
58
+ end
59
+
60
+
61
+ def headerSize()
62
+ return @info[:HeaderParagraphs] * PARA
63
+ end
64
+
65
+
66
+ def fileSize()
67
+ sz = @info[:BlocksInFile] * BLK
68
+ if @info[:BytesInLastBlock] != 0
69
+ sz -= BLK - @info[:BytesInLastBlock]
70
+ end
71
+ return sz
72
+ end
73
+
74
+
75
+ def getRelocation(idx)
76
+ raise "Wrong relocation index " if idx<0 || idx>@info[:NumberOfRelocations]
77
+ return getData(@info[:RelocTableOffset] + idx * 4, 4).unpack('vv')
78
+ end
79
+
80
+
81
+ def freeSpace(middle = false)
82
+ return @info[:RelocTableOffset] - HSIZE if middle
83
+ return headerSize() - HSIZE - @info[:NumberOfRelocations] * 4
84
+ end
85
+
86
+
87
+ def print(what, how)
88
+ mode(parseHow(how))
89
+ if what == "header"
90
+ ofs=0
91
+ @info.each{|k,v|
92
+ printf("%20s:\t%s\n", k.to_s, colVal(ofs, 2))
93
+ ofs+=2
94
+ }
95
+ puts
96
+ fsz = fileSize()
97
+ hsz = headerSize()
98
+ s = colStr(sprintf("%d (%X)", fsz,fsz), changed?(2,4))
99
+ printf("mz file size: %s\treal file size: %d (0x%X)\n", s, @fsize, @fsize)
100
+ printf("header size: %s\n", colStr(hsz, changed?(8)))
101
+ printf("code size: %s\n", colStr(fsz - hsz, @mz.body.add != nil))
102
+ printf("reloc table size: %s\n", colStr(@info[:NumberOfRelocations] * 4, changed?(6)))
103
+ printf("free space in header: before relocs 0x%X, after relocs 0x%X\n", freeSpace(true), freeSpace())
104
+ return true
105
+ end
106
+ if what == "reloc"
107
+ ofs = @info[:RelocTableOffset]
108
+ for i in 0..@info[:NumberOfRelocations]-1
109
+ s1 = colVal(ofs,2)
110
+ s2 = colVal(ofs+2,2)
111
+ s3 = @mz.body.segData(getRelocation(i), 2, true)
112
+ printf("%08X\t%s:%s\t= %s\n", ofs, s2, s1, s3)
113
+ ofs += 4
114
+ end
115
+ return true
116
+ end
117
+ return super(what, how)
118
+ end
119
+
120
+
121
+ end
122
+
123
+ end
@@ -0,0 +1,82 @@
1
+ require 'singleton'
2
+
3
+ module Resedit
4
+
5
+ class MZEnv
6
+
7
+ include Singleton
8
+
9
+ def set(name,value)
10
+ MZEnv.class_eval{
11
+ define_method(name){ s2i(value) }
12
+ }
13
+ end
14
+
15
+ def s2i_nt(str)
16
+ return [s2i(str), true]
17
+ rescue Exception
18
+ return [0, false]
19
+ end
20
+
21
+ def s2i(str)
22
+ ss=str.split(':')
23
+ if ss.length == 2
24
+ ss[0] = '0x'+ss[0] if ss[0][0,2]!='0x'
25
+ ss[1] = '0x'+ss[1] if ss[1][0,2]!='0x'
26
+ return (s2i(ss[0]) << 4) + s2i(ss[1])
27
+ end
28
+ return eval(str, binding())
29
+ end
30
+
31
+
32
+ def valueHex(s, type)
33
+ s = s[0..-2] if s[-1] == 'h'
34
+ s = s[2..-1] if s[0,2] == '0x'
35
+ return nil if s.length == 0
36
+ sz = type[1]
37
+ sz = s.length / 2 + s.length % 2 if !sz || sz==0
38
+ hx = eval('0x'+s, binding())
39
+ s=""
40
+ for i in 0..sz-1
41
+ s += sprintf("%02X", hx & 0xFF)
42
+ hx >>= 8
43
+ end
44
+ return valueBytes(s)
45
+ rescue SyntaxError
46
+ return nil
47
+ end
48
+
49
+ def valueBytes(str)
50
+ return nil if str[0,2] == '0x' || str[0,2]=='0X'
51
+ return nil if str.length % 2 == 1
52
+ return nil if str[/\H/]
53
+ return [str].pack('H*')
54
+ rescue
55
+ return nil
56
+ end
57
+
58
+ def value2bytes(str, type)
59
+ tp = [nil, nil]
60
+ if type && type.length > 0
61
+ tp[0] = type[0]
62
+ t = type[1..-1]
63
+ t = t[1..-1] while t.length > 0 && (t[0]<'0' || t[0]>'9')
64
+ tp[1] = t.to_i
65
+ end
66
+ if tp[0]=='f' || (File.exists?(str) && !tp[0])
67
+ return File.read(str)
68
+ end
69
+ res = valueBytes(str) if !tp[0] || tp[0] == "b"
70
+ res = valueHex(str, tp) if !res && (!tp[0] || tp[0] == "h")
71
+ res = eval(str, binding()) if !res
72
+ return res if res.is_a?(String)
73
+ res = valueHex(res.to_s(16), tp)
74
+ raise str if !res
75
+ return res
76
+ rescue Exception => e
77
+ raise "Bad value: "+e.to_s
78
+ end
79
+
80
+ end
81
+
82
+ end