pgtools 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.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +25 -0
  3. data/bin/bxm_decoder +2 -0
  4. data/bin/bxm_encoder +2 -0
  5. data/bin/clh_convert +2 -0
  6. data/bin/clp_convert +2 -0
  7. data/bin/clw_convert +2 -0
  8. data/bin/dat_creator +2 -0
  9. data/bin/dat_extractor +2 -0
  10. data/bin/dat_ls +2 -0
  11. data/bin/eff_idd_creator +2 -0
  12. data/bin/eff_idd_extractor +2 -0
  13. data/bin/exp_convert_wiiu_pc +2 -0
  14. data/bin/exp_tool +2 -0
  15. data/bin/mot_convert_wiiu_pc +2 -0
  16. data/bin/mot_tool +2 -0
  17. data/bin/pkz_extractor +2 -0
  18. data/bin/scr_creator +2 -0
  19. data/bin/scr_extractor +2 -0
  20. data/bin/wmb_cleanup +2 -0
  21. data/bin/wmb_common_bones +2 -0
  22. data/bin/wmb_convert_pc_switch +2 -0
  23. data/bin/wmb_convert_wiiu_pc +2 -0
  24. data/bin/wmb_export_assimp +2 -0
  25. data/bin/wmb_get_bone_map +2 -0
  26. data/bin/wmb_import_assimp +2 -0
  27. data/bin/wmb_import_nier +2 -0
  28. data/bin/wmb_import_wiiu +2 -0
  29. data/bin/wtb_convert_wiiu_pc +2 -0
  30. data/bin/wtx_creator +2 -0
  31. data/bin/wtx_extractor +2 -0
  32. data/lib/bayonetta/alignment.rb +14 -0
  33. data/lib/bayonetta/bone.rb +55 -0
  34. data/lib/bayonetta/bxm.rb +180 -0
  35. data/lib/bayonetta/clh.rb +159 -0
  36. data/lib/bayonetta/clp.rb +212 -0
  37. data/lib/bayonetta/clw.rb +166 -0
  38. data/lib/bayonetta/dat.rb +261 -0
  39. data/lib/bayonetta/eff.rb +314 -0
  40. data/lib/bayonetta/endianness.rb +53 -0
  41. data/lib/bayonetta/exp.rb +768 -0
  42. data/lib/bayonetta/linalg.rb +416 -0
  43. data/lib/bayonetta/material_database.yaml +2581 -0
  44. data/lib/bayonetta/mot.rb +763 -0
  45. data/lib/bayonetta/pkz.rb +63 -0
  46. data/lib/bayonetta/scr.rb +393 -0
  47. data/lib/bayonetta/tools/bxm_decoder.rb +23 -0
  48. data/lib/bayonetta/tools/bxm_encoder.rb +37 -0
  49. data/lib/bayonetta/tools/clh_convert.rb +60 -0
  50. data/lib/bayonetta/tools/clp_convert.rb +70 -0
  51. data/lib/bayonetta/tools/clw_convert.rb +60 -0
  52. data/lib/bayonetta/tools/dat_creator.rb +57 -0
  53. data/lib/bayonetta/tools/dat_extractor.rb +94 -0
  54. data/lib/bayonetta/tools/dat_ls.rb +106 -0
  55. data/lib/bayonetta/tools/eff_idd_creator.rb +66 -0
  56. data/lib/bayonetta/tools/eff_idd_extractor.rb +73 -0
  57. data/lib/bayonetta/tools/exp_convert_wiiu_pc.rb +33 -0
  58. data/lib/bayonetta/tools/exp_tool.rb +48 -0
  59. data/lib/bayonetta/tools/mot_convert_wiiu_pc.rb +33 -0
  60. data/lib/bayonetta/tools/mot_tool.rb +60 -0
  61. data/lib/bayonetta/tools/pkz_extractor.rb +75 -0
  62. data/lib/bayonetta/tools/scr_creator.rb +63 -0
  63. data/lib/bayonetta/tools/scr_extractor.rb +78 -0
  64. data/lib/bayonetta/tools/wmb_cleanup.rb +250 -0
  65. data/lib/bayonetta/tools/wmb_common_bones.rb +45 -0
  66. data/lib/bayonetta/tools/wmb_convert_pc_switch.rb +35 -0
  67. data/lib/bayonetta/tools/wmb_convert_wiiu_pc.rb +33 -0
  68. data/lib/bayonetta/tools/wmb_export_assimp.rb +479 -0
  69. data/lib/bayonetta/tools/wmb_get_bone_map.rb +50 -0
  70. data/lib/bayonetta/tools/wmb_import_assimp.rb +735 -0
  71. data/lib/bayonetta/tools/wmb_import_geometry_wiiu_pc.rb +472 -0
  72. data/lib/bayonetta/tools/wmb_import_nier.rb +309 -0
  73. data/lib/bayonetta/tools/wtb_convert_wiiu_pc.rb +95 -0
  74. data/lib/bayonetta/tools/wtb_import_textures.rb +103 -0
  75. data/lib/bayonetta/tools/wtx_creator.rb +69 -0
  76. data/lib/bayonetta/tools/wtx_extractor.rb +85 -0
  77. data/lib/bayonetta/vertex_types.yaml +213 -0
  78. data/lib/bayonetta/vertex_types2.yaml +164 -0
  79. data/lib/bayonetta/vertex_types_nier.yaml +145 -0
  80. data/lib/bayonetta/wmb.rb +2443 -0
  81. data/lib/bayonetta/wmb3.rb +759 -0
  82. data/lib/bayonetta/wtb.rb +481 -0
  83. data/lib/bayonetta.rb +60 -0
  84. metadata +254 -0
@@ -0,0 +1,212 @@
1
+ module Bayonetta
2
+
3
+ class CLPFile < LibBin::Structure
4
+
5
+ class FVector < LibBin::Structure
6
+ float :x
7
+ float :y
8
+ float :z
9
+
10
+ def from_bxm_vector(v)
11
+ @x, @y, @z = v.split(" ").collect(&:to_f)
12
+ self
13
+ end
14
+
15
+ def self.from_bxm_vector(v)
16
+ nv = self::new
17
+ nv.from_bxm_vector(v)
18
+ nv
19
+ end
20
+
21
+ end
22
+
23
+ class Header < LibBin::Structure
24
+ int32 :num
25
+ float :limit_spring_rate
26
+ float :spd_rate
27
+ float :stretchy
28
+ int16 :bundle_num
29
+ int16 :bundle_num_2
30
+ float :thick
31
+ register_field :gravity_vec, FVector
32
+ int32 :gravity_parts_no
33
+ float :first_bundle_rate
34
+ register_field :wind_vec, FVector
35
+ int32 :wind_parts_no
36
+ register_field :wind_offset, FVector
37
+ float :wind_sin
38
+ float :hit_adjust_rate
39
+
40
+ def from_bxm_header(h)
41
+ @num = h.at_css("m_Num").content.to_i
42
+ @limit_spring_rate = h.at_css("m_LimitSpringRate").content.to_f
43
+ @spd_rate = h.at_css("m_SpdRate").content.to_f
44
+ @stretchy = h.at_css("m_Stretchy").content.to_f
45
+ @bundle_num = h.at_css("m_BundleNum").content.to_i
46
+ @bundle_num_2 = h.at_css("m_BundleNum2").content.to_i
47
+ @thick = h.at_css("m_Thick").content.to_f
48
+ @gravity_vec = FVector.from_bxm_vector(h.at_css("m_GravityVec").content)
49
+ @gravity_parts_no = h.at_css("m_GravityPartsNo").content.to_i
50
+ @first_bundle_rate = h.at_css("m_FirstBundleRate").content.to_f
51
+ @wind_vec = FVector.from_bxm_vector(h.at_css("m_WindVec").content)
52
+ @wind_parts_no = h.at_css("m_WindPartsNo").content.to_i
53
+ @wind_offset = FVector.from_bxm_vector(h.at_css("m_WindOffset").content)
54
+ @wind_sin = h.at_css("m_WindSin").content.to_f
55
+ @hit_adjust_rate = h.at_css("m_HitAdjustRate").content.to_f
56
+ self
57
+ end
58
+
59
+ def self.from_bxm_header(h)
60
+ nh = self::new
61
+ nh.from_bxm_header(h)
62
+ nh
63
+ end
64
+
65
+ end
66
+
67
+ class Cloth < LibBin::Structure
68
+ int16 :no
69
+ int16 :no_up
70
+ int16 :no_down
71
+ int16 :no_side
72
+ int16 :no_poly
73
+ int16 :no_fix
74
+ float :rot_limit
75
+ register_field :offset, FVector
76
+
77
+ def from_bxm_cloth(h)
78
+ @no = h.at_css("no").content.to_i
79
+ @no_up = h.at_css("noUp").content.to_i
80
+ @no_down = h.at_css("noDown").content.to_i
81
+ @no_side = h.at_css("noSide").content.to_i
82
+ @no_poly = h.at_css("noPoly").content.to_i
83
+ @no_fix = h.at_css("noFix").content.to_i
84
+ @rot_limit = h.at_css("rotLimit").content.to_f
85
+ @offset = FVector.from_bxm_vector(h.at_css("offset").content)
86
+ self
87
+ end
88
+
89
+ def self.from_bxm_cloth(c)
90
+ cl = self::new
91
+ cl.from_bxm_cloth(c)
92
+ cl
93
+ end
94
+
95
+ def remap(map, poly = true, fix = true)
96
+ @no = check_and_remap_bone(map, @no)
97
+ @no_up = check_and_remap_bone(map, @no_up)
98
+ @no_down = check_and_remap_bone(map, @no_down)
99
+ @no_side = check_and_remap_bone(map, @no_side)
100
+ @no_poly = ( poly ? check_and_remap_bone(map, @no_poly) : 4095 )
101
+ @no_fix = ( fix ? check_and_remap_bone(map, @no_fix) : 4095 )
102
+ end
103
+
104
+ private
105
+ def check_and_remap_bone(map, no)
106
+ tmp = map[no]
107
+ raise "No bone found in map for #{no}!" unless tmp
108
+ tmp
109
+ end
110
+
111
+ end
112
+
113
+ register_field :header, Header
114
+ register_field :cloth, Cloth, count: 'header\num'
115
+
116
+ def was_big?
117
+ @__was_big
118
+ end
119
+
120
+ def remap(map, poly = true, fix = true)
121
+ @cloth.each { |c|
122
+ c.remap(map, poly, fix)
123
+ }
124
+ end
125
+
126
+ def self.load_bxm(input_name)
127
+ bxm = BXMFile::load(input_name)
128
+ clp = self::new
129
+
130
+ doc = bxm.to_xml
131
+ h = doc.at_css("//CLOTH//CLOTH_HEADER")
132
+ clp.header = Header.from_bxm_header(h)
133
+
134
+ clp.cloth = doc.css("//CLOTH//CLOTH_WK").collect { |c|
135
+ Cloth.from_bxm_cloth(c)
136
+ }
137
+ clp
138
+ end
139
+
140
+ def self.is_big?(f)
141
+ f.rewind
142
+ f.size
143
+ block = lambda { |int|
144
+ num = f.read(4).unpack(int).first
145
+ ( num >= 0 ) && ( num < 256 )
146
+ }
147
+ big = block.call("l>")
148
+ f.rewind
149
+ small = block.call("l<")
150
+ f.rewind
151
+ raise "Invalid data!" unless big ^ small
152
+ return big
153
+ end
154
+
155
+ def self.convert(input_name, output_name, output_big = false)
156
+ if input_name.respond_to?(:read) && input_name.respond_to?(:seek)
157
+ input = input_name
158
+ else
159
+ input = File.open(input_name, "rb")
160
+ end
161
+ input_big = is_big?(input)
162
+
163
+ if output_name.respond_to?(:write) && output_name.respond_to?(:seek)
164
+ output = output_name
165
+ else
166
+ output = File.open(output_name, "wb")
167
+ end
168
+ output.rewind
169
+
170
+ clp = self::new
171
+ clp.instance_variable_set(:@__was_big, input_big)
172
+ clp.__convert(input, output, input_big, output_big)
173
+
174
+ input.close unless input_name.respond_to?(:read) && input_name.respond_to?(:seek)
175
+ output.close unless output_name.respond_to?(:write) && output_name.respond_to?(:seek)
176
+ clp
177
+ end
178
+
179
+ def self.load(input_name)
180
+ if input_name.respond_to?(:read) && input_name.respond_to?(:seek)
181
+ input = input_name
182
+ else
183
+ input = File.open(input_name, "rb")
184
+ end
185
+ input_big = is_big?(input)
186
+
187
+ clp = self::new
188
+ clp.instance_variable_set(:@__was_big, input_big)
189
+ clp.__load(input, input_big)
190
+ input.close unless input_name.respond_to?(:read) && input_name.respond_to?(:seek)
191
+ clp
192
+ end
193
+
194
+ def dump(output_name, output_big = false)
195
+ if output_name.respond_to?(:write) && output_name.respond_to?(:seek)
196
+ output = output_name
197
+ else
198
+ output = File.open(output_name, "wb")
199
+ end
200
+ output.rewind
201
+
202
+ __set_dump_state(output, output_big, nil, nil)
203
+ __dump_fields
204
+ __unset_dump_state
205
+
206
+ output.close unless output_name.respond_to?(:write) && output_name.respond_to?(:seek)
207
+ self
208
+ end
209
+
210
+ end
211
+
212
+ end
@@ -0,0 +1,166 @@
1
+ module Bayonetta
2
+
3
+ class CLWFile < LibBin::Structure
4
+
5
+ class FVector < LibBin::Structure
6
+ float :x
7
+ float :y
8
+ float :z
9
+
10
+ def from_bxm_vector(v)
11
+ @x, @y, @z = v.split(" ").collect(&:to_f)
12
+ self
13
+ end
14
+
15
+ def self.from_bxm_vector(v)
16
+ nv = self::new
17
+ nv.from_bxm_vector(v)
18
+ nv
19
+ end
20
+
21
+ end
22
+
23
+ class ClothWind < LibBin::Structure
24
+ int32 :wind_type
25
+ int32 :parts_no
26
+ register_field :offset, FVector
27
+ register_field :offset2, FVector
28
+ float :radius
29
+ float :power
30
+ float :timer
31
+ float :swing_rate
32
+ float :swing_spd
33
+ float :delay_max
34
+
35
+ def from_bxm_cloth_wind(h)
36
+ @wind_type = h.at_css("WindType").content.to_i
37
+ @parts_no = h.at_css("PartsNo").content.to_i
38
+ @offset = FVector.from_bxm_vector(h.at_css("Offset").content)
39
+ @offset2 = FVector.from_bxm_vector(h.at_css("Offset2").content)
40
+ @radius = h.at_css("Radius").content.to_f
41
+ @power = h.at_css("Power").content.to_f
42
+ @timer = h.at_css("Timer").content.to_f
43
+ @swing_rate = h.at_css("SwingRate").content.to_f
44
+ @swing_spd = h.at_css("SwingSpd").content.to_f
45
+ @delay_max = h.at_css("DelayMax").content.to_f
46
+ self
47
+ end
48
+
49
+ def self.from_bxm_cloth_wind(c)
50
+ clw = self::new
51
+ clw.from_bxm_cloth_wind(c)
52
+ clw
53
+ end
54
+
55
+ def remap(map)
56
+ @parts_no = check_and_remap_bone(map, @parts_no)
57
+ end
58
+
59
+ private
60
+ def check_and_remap_bone(map, no)
61
+ tmp = map[no]
62
+ raise "No bone found in map for #{no}!" unless tmp
63
+ tmp
64
+ end
65
+
66
+ end
67
+
68
+ int32 :cloth_wind_num
69
+ register_field :cloth_wind, ClothWind, count: 'cloth_wind_num'
70
+
71
+ def was_big?
72
+ @__was_big
73
+ end
74
+
75
+ def remap(map)
76
+ @cloth_wind.each { |c|
77
+ c.remap(map)
78
+ }
79
+ end
80
+
81
+ def self.load_bxm(input_name)
82
+ bxm = BXMFile::load(input_name)
83
+ clw = self::new
84
+
85
+ doc = bxm.to_xml
86
+ clw.cloth_wind_num = doc.at_css("//CLOTH_WIND//CLOTH_WIND_NUM").content.to_i
87
+
88
+ clw.cloth_wind = doc.css("//CLOTH_WIND//CLOTH_WIND_WK").collect { |c|
89
+ ClothWind.from_bxm_cloth_wind(c)
90
+ }
91
+ clw
92
+ end
93
+
94
+ def self.is_big?(f)
95
+ f.rewind
96
+ f.size
97
+ block = lambda { |int|
98
+ num = f.read(4).unpack(int).first
99
+ ( num >= 0 ) && ( num < 256 )
100
+ }
101
+ big = block.call("l>")
102
+ f.rewind
103
+ small = block.call("l<")
104
+ f.rewind
105
+ raise "Invalid data!" unless big ^ small
106
+ return big
107
+ end
108
+
109
+ def self.convert(input_name, output_name, output_big = false)
110
+ if input_name.respond_to?(:read) && input_name.respond_to?(:seek)
111
+ input = input_name
112
+ else
113
+ input = File.open(input_name, "rb")
114
+ end
115
+ input_big = is_big?(input)
116
+
117
+ if output_name.respond_to?(:write) && output_name.respond_to?(:seek)
118
+ output = output_name
119
+ else
120
+ output = File.open(output_name, "wb")
121
+ end
122
+ output.rewind
123
+
124
+ clp = self::new
125
+ clp.instance_variable_set(:@__was_big, input_big)
126
+ clp.__convert(input, output, input_big, output_big)
127
+
128
+ input.close unless input_name.respond_to?(:read) && input_name.respond_to?(:seek)
129
+ output.close unless output_name.respond_to?(:write) && output_name.respond_to?(:seek)
130
+ clp
131
+ end
132
+
133
+ def self.load(input_name)
134
+ if input_name.respond_to?(:read) && input_name.respond_to?(:seek)
135
+ input = input_name
136
+ else
137
+ input = File.open(input_name, "rb")
138
+ end
139
+ input_big = is_big?(input)
140
+
141
+ clp = self::new
142
+ clp.instance_variable_set(:@__was_big, input_big)
143
+ clp.__load(input, input_big)
144
+ input.close unless input_name.respond_to?(:read) && input_name.respond_to?(:seek)
145
+ clp
146
+ end
147
+
148
+ def dump(output_name, output_big = false)
149
+ if output_name.respond_to?(:write) && output_name.respond_to?(:seek)
150
+ output = output_name
151
+ else
152
+ output = File.open(output_name, "wb")
153
+ end
154
+ output.rewind
155
+
156
+ __set_dump_state(output, output_big, nil, nil)
157
+ __dump_fields
158
+ __unset_dump_state
159
+
160
+ output.close unless output_name.respond_to?(:write) && output_name.respond_to?(:seek)
161
+ self
162
+ end
163
+
164
+ end
165
+
166
+ end
@@ -0,0 +1,261 @@
1
+ require 'stringio'
2
+ module Bayonetta
3
+ class DATFile < LibBin::Structure
4
+ include Alignment
5
+ attr_reader :big
6
+ ALIGNMENTS = {
7
+ 'dat' => 0x2000,
8
+ 'wmb' => 0x1000,
9
+ 'wtb' => 0x1000,
10
+ 'wtp' => 0x1000,
11
+ 'wta' => 0x40,
12
+ 'exp' => 0x1000,
13
+ 'sop' => 0x40,
14
+ 'eff' => 0x1000,
15
+ 'sdx' => 0x1000,
16
+ 'bxm' => 0x40
17
+ }
18
+ ALIGNMENTS.default = 0x10
19
+
20
+ class Header < LibBin::Structure
21
+ string :id, 4
22
+ uint32 :num_files
23
+ uint32 :offset_file_offsets
24
+ uint32 :offset_file_extensions
25
+ uint32 :offset_file_names
26
+ uint32 :offset_file_sizes
27
+ uint32 :offset_hash_map
28
+ end
29
+
30
+ class HashMap < LibBin::Structure
31
+ class Header < LibBin::Structure
32
+ uint32 :pre_hash_shift
33
+ uint32 :offset_bucket_ranks
34
+ uint32 :offset_hashes
35
+ uint32 :offset_file_indices
36
+ end
37
+ register_field :header, Header
38
+ int16 :bucket_ranks, count: '(1<<(31 - header\pre_hash_shift))', offset: 'header\offset_bucket_ranks', relative_offset: true
39
+ uint32 :hashes, count: '..\header\num_files', offset: 'header\offset_hashes', relative_offset: true
40
+ uint16 :file_indices, count: '..\header\num_files', offset: 'header\offset_file_indices', relative_offset: true
41
+
42
+ def get
43
+ {
44
+ pre_hash_shift: @header.pre_hash_shift,
45
+ hashes: file_indices.zip(hashes).sort { |(i1 ,h1), (i2, h2)|
46
+ i1 <=> i2
47
+ }.collect { |i, h|
48
+ h
49
+ }
50
+ }
51
+ end
52
+
53
+ def initialize
54
+ super
55
+ @header = Header::new
56
+ end
57
+
58
+ def set(hash_map)
59
+ bit_shift = hash_map[:pre_hash_shift]
60
+ hash_list = hash_map[:hashes]
61
+ num_files = hash_list.size
62
+ @header.pre_hash_shift = bit_shift
63
+ buckets = Hash::new { |h, k| h[k] = [] }
64
+ hash_list.each_with_index { |h, i|
65
+ bucket_index = h >> @header.pre_hash_shift
66
+ buckets[bucket_index].push [h, i]
67
+ }
68
+ @bucket_ranks = []
69
+ @hashes = []
70
+ @file_indices = []
71
+ bucket_rank = 0
72
+ num_buckets = (1 << (31 - header.pre_hash_shift))
73
+ num_buckets.times { |i|
74
+ if buckets.has_key?(i)
75
+ @bucket_ranks.push bucket_rank
76
+ bucket_rank += buckets[i].size
77
+ buckets[i].each { |h, ind|
78
+ @hashes.push h
79
+ @file_indices.push ind
80
+ }
81
+ else
82
+ @bucket_ranks.push -1
83
+ end
84
+ }
85
+ @header.offset_bucket_ranks = 0x10
86
+ @header.offset_hashes = header.offset_bucket_ranks + num_buckets * 2
87
+ @header.offset_file_indices = header.offset_hashes + num_files * 4
88
+ self
89
+ end
90
+ end
91
+
92
+ register_field :header, Header
93
+ uint32 :file_offsets, count: 'header\num_files', offset: 'header\offset_file_offsets'
94
+ string :file_extensions, 4, count: 'header\num_files', offset: 'header\offset_file_extensions'
95
+ uint32 :file_name_length, offset: 'header\offset_file_names'
96
+ string :file_names, count: 'header\num_files', offset: 'header\offset_file_names + 4 + __iterator * file_name_length', sequence: true
97
+ uint32 :file_sizes, count: 'header\num_files', offset: 'header\offset_file_sizes'
98
+ register_field :hash_map, HashMap, offset: 'header\offset_hash_map'
99
+ string :files, 'file_sizes[__iterator]', count: 'header\num_files', offset: 'file_offsets[__iterator]', sequence: true
100
+
101
+ def self.is_big?(f)
102
+ f.rewind
103
+ block = lambda { |big|
104
+ h = Header::load(f, big)
105
+ h.offset_file_offsets < f.size &&
106
+ h.offset_file_names < f.size &&
107
+ h.offset_file_sizes < f.size &&
108
+ h.offset_hash_map < f.size
109
+ }
110
+ big = block.call(true)
111
+ f.rewind
112
+ small = block.call(false)
113
+ f.rewind
114
+ raise "Invalid data!" unless big ^ small
115
+ return big
116
+ end
117
+
118
+ def initialize(big = false)
119
+ @big = big
120
+ super()
121
+ @header = Header::new
122
+ @header.id = "DAT\x00".b
123
+ @header.num_files = 0
124
+ @header.offset_file_offsets = 0
125
+ @header.offset_file_extensions = 0
126
+ @header.offset_file_names = 0
127
+ @header.offset_file_sizes = 0
128
+ @header.offset_hash_map = 0
129
+
130
+ @file_offsets = []
131
+ @file_extensions = []
132
+ @file_name_length = 0
133
+ @file_names = []
134
+ @file_sizes = []
135
+ @files = []
136
+
137
+ @hash_map = nil
138
+ end
139
+
140
+ def invalidate_layout
141
+ @header.offset_file_offsets = 0
142
+ @header.offset_file_extensions = 0
143
+ @header.offset_file_names = 0
144
+ @header.offset_file_sizes = 0
145
+ @header.offset_hash_map = 0
146
+ @file_offsets = []
147
+ @hash_map = nil
148
+ self
149
+ end
150
+
151
+ def layout
152
+ @file_names.collect { |name| name[0..-2] }
153
+ end
154
+
155
+ def each
156
+ if block_given? then
157
+ @header.num_files.times { |i|
158
+ yield @file_names[i][0..-2], StringIO::new(@files[i] ? @files[i] : "", "rb")
159
+ }
160
+ else
161
+ to_enum(:each)
162
+ end
163
+ end
164
+
165
+ def [](i)
166
+ return [@file_names[i][0..-2], StringIO::new(@files[i] ? @files[i] : "", "rb")]
167
+ end
168
+
169
+ def push(name, file)
170
+ invalidate_layout
171
+ @file_names.push name+"\x00"
172
+ if file.kind_of?(StringIO)
173
+ data = file.string
174
+ else
175
+ file.rewind
176
+ data = file.read
177
+ end
178
+ @files.push data
179
+ @file_sizes.push file.size
180
+ extname = File.extname(name)
181
+ raise "Invalid name, missing extension!" if extname == ""
182
+ @file_extensions.push extname[1..-1]+"\x00"
183
+ @header.num_files += 1
184
+ self
185
+ end
186
+
187
+ def compute_layout
188
+ @header.offset_file_offsets = 0x20
189
+ @header.offset_file_extensions = @header.offset_file_offsets + 4 * @header.num_files
190
+ @header.offset_file_names = @header.offset_file_extensions + 4 * @header.num_files
191
+ max_file_name_length = @file_names.collect(&:length).max
192
+ @file_name_length = max_file_name_length
193
+ @header.offset_file_sizes = @header.offset_file_names + 4 + @file_name_length * @header.num_files
194
+ @header.offset_file_sizes = align(@header.offset_file_sizes, 4)
195
+ if @hash_map
196
+ @header.offset_hash_map = @header.offset_file_sizes + 4 * @header.num_files
197
+ files_offset = @header.offset_hash_map + @hash_map.__size(@header.offset_hash_map, self)
198
+ else
199
+ @offset_hash_map = 0
200
+ files_offset = @header.offset_file_sizes + 4 * @header.num_files
201
+ end
202
+ @file_offsets = @header.num_files.times.collect { |i|
203
+ if @file_sizes[i] > 0
204
+ tmp = align(files_offset, ALIGNMENTS[@file_extensions[i][0..-2]])
205
+ files_offset = align(tmp + @file_sizes[i], ALIGNMENTS[@file_extensions[i][0..-2]])
206
+ tmp
207
+ else
208
+ 0
209
+ end
210
+ }
211
+ @total_size = align(files_offset, 0x1000)
212
+ self
213
+ end
214
+
215
+ def set_hash_map(hash)
216
+ @hash_map = HashMap::new
217
+ @hash_map.set hash
218
+ end
219
+
220
+ def self.load(input_name)
221
+ if input_name.respond_to?(:read) && input_name.respond_to?(:seek)
222
+ input = input_name
223
+ else
224
+ File.open(input_name, "rb") { |f|
225
+ input = StringIO::new(f.read, "rb")
226
+ }
227
+ end
228
+ big = self::is_big?(input)
229
+ dat = self::new(big)
230
+ dat.__load(input, big)
231
+ input.close unless input_name.respond_to?(:read) && input_name.respond_to?(:seek)
232
+ dat
233
+ end
234
+
235
+ def dump(output_name)
236
+ compute_layout
237
+ if output_name.respond_to?(:write) && output_name.respond_to?(:seek)
238
+ output = output_name
239
+ else
240
+ output = StringIO::new("", "wb")#File.open(output_name, "wb")
241
+ output.write("\x00"*@total_size)
242
+ output.rewind
243
+ end
244
+ output.rewind
245
+
246
+ __set_dump_state(output, @big, nil, nil)
247
+ __dump_fields
248
+ __unset_dump_state
249
+
250
+ unless output_name.respond_to?(:write) && output_name.respond_to?(:seek)
251
+ File.open(output_name, "wb") { |f|
252
+ f.write output.string
253
+ }
254
+ output.close
255
+ end
256
+ self
257
+ end
258
+
259
+ end
260
+
261
+ end