resedit 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/resedit +11 -0
- data/lib/resedit.rb +17 -0
- data/lib/resedit/app/app.rb +160 -0
- data/lib/resedit/app/app_command.rb +100 -0
- data/lib/resedit/app/colorizer.rb +34 -0
- data/lib/resedit/app/font_convert.rb +54 -0
- data/lib/resedit/app/io_commands.rb +74 -0
- data/lib/resedit/app/mz_command.rb +192 -0
- data/lib/resedit/app/std_commands.rb +69 -0
- data/lib/resedit/app/text_convert.rb +60 -0
- data/lib/resedit/convert/bitconv.rb +48 -0
- data/lib/resedit/font/font.rb +82 -0
- data/lib/resedit/font/font_char.rb +49 -0
- data/lib/resedit/image/image.rb +47 -0
- data/lib/resedit/image/image_factory.rb +33 -0
- data/lib/resedit/image/png_image.rb +38 -0
- data/lib/resedit/mz/changeable.rb +248 -0
- data/lib/resedit/mz/hexwriter.rb +88 -0
- data/lib/resedit/mz/mz.rb +166 -0
- data/lib/resedit/mz/mz_body.rb +141 -0
- data/lib/resedit/mz/mz_header.rb +123 -0
- data/lib/resedit/mz/mzenv.rb +82 -0
- data/lib/resedit/text/conv_keybru.rb +84 -0
- data/lib/resedit/text/conv_table.rb +10 -0
- data/lib/resedit/text/escaper.rb +117 -0
- data/lib/resedit/text/format_text.rb +44 -0
- data/lib/resedit/text/format_xml.rb +46 -0
- data/lib/resedit/text/huffman.rb +190 -0
- data/lib/resedit/text/text.rb +73 -0
- metadata +86 -0
@@ -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
|