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