vpk 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in vpk.gemspec
4
+ gemspec
5
+
@@ -0,0 +1,5 @@
1
+ vpk
2
+ ===
3
+
4
+ VPK File Format Parser (extract and archive)
5
+
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,324 @@
1
+ require "vpk/version"
2
+ require 'logger'
3
+ require 'zlib'
4
+ require 'find'
5
+ require 'stringio'
6
+
7
+ module VPK
8
+ class VPKHeader
9
+ attr_accessor :signature
10
+ attr_accessor :version
11
+ attr_accessor :directory_length
12
+
13
+ VPK_SIGNATURE = 0x55aa1234
14
+ VERSION = 1
15
+ end
16
+
17
+ class VPKDirectoryEntry
18
+ attr_accessor :extension
19
+ attr_accessor :path
20
+ attr_accessor :file
21
+
22
+ attr_accessor :crc
23
+ attr_accessor :preload_bytes
24
+ attr_accessor :archive_index
25
+ attr_accessor :entry_offset
26
+ attr_accessor :entry_length
27
+ attr_accessor :terminator
28
+
29
+ attr_accessor :payload
30
+
31
+ def self.load_from(path)
32
+ entry = VPKDirectoryEntry.new
33
+ entry.payload = File.read(path)
34
+ entry.full_path = path
35
+ entry
36
+ end
37
+
38
+ def full_path
39
+ if path == " "
40
+ "#{file}.#{extension}"
41
+ else
42
+ "#{path}/#{file}.#{extension}"
43
+ end
44
+ end
45
+
46
+ def full_path=(path)
47
+ (path, file) = File.split(path)
48
+ ext = File.extname(file)
49
+
50
+ @path = path.gsub(/^\.\.?\//, "")
51
+ @extension = ext.gsub(/^./, "")
52
+ @file = File.basename(file, ext)
53
+ end
54
+
55
+ def read_payload(io, end_of_header = 0)
56
+ io.seek(end_of_header + @entry_offset)
57
+ @payload = io.read(@entry_length + @preload_bytes)
58
+ end
59
+
60
+ def valid?
61
+ @crc == Zlib.crc32(@payload)
62
+ end
63
+
64
+ def mkdir_p
65
+ FileUtils.mkdir_p(@path)
66
+ end
67
+
68
+ def write_to_file(bath_path = nil)
69
+ File.open(full_path, "wb") do |file|
70
+ file.write @payload
71
+ end
72
+ end
73
+ end
74
+
75
+ class VPKFile
76
+ attr_accessor :header
77
+ attr_accessor :dir_entries
78
+
79
+ @@logger = Logger.new(nil)
80
+ def self.logger=(new_logger)
81
+ @@logger = new_logger
82
+ end
83
+
84
+ def initialize(path=nil)
85
+ return if path.nil?
86
+
87
+ @@logger.info "extract vpk file #{path.inspect}"
88
+ File.open(path, "rb") do |io|
89
+ @header = VPKHeader.new
90
+ @header.signature = io.read(4).unpack("V*").first
91
+ @header.version = io.read(4).unpack("V*").first
92
+ @header.directory_length = io.read(4).unpack("V*").first
93
+ @@logger.info "reading header: #{@header.inspect}"
94
+ end_of_header = io.pos
95
+
96
+ unless @header.signature == "0x55aa1234".hex
97
+ @@logger.error "signature is invalid: #{@header.signature.inspect}"
98
+ raise VPKFileFormatError
99
+ end
100
+
101
+ @@logger.info "signature is valid"
102
+
103
+ @dir_entries = read_directory(io)
104
+
105
+ @dir_entries.each do |entry|
106
+ @@logger.info "try to read #{entry.full_path} (#{entry.entry_length} bytes)"
107
+ entry.read_payload(io, end_of_header + @header.directory_length)
108
+ unless entry.valid?
109
+ @@logger.error "crc is invalid: #{entry.crc.inspect}"
110
+ raise VPKFileInvalidCRCError
111
+ end
112
+ @@logger.info "done"
113
+ end
114
+ end
115
+ end
116
+
117
+ def self.open(path, &block)
118
+ file = self.new(path)
119
+ block.call file
120
+ end
121
+
122
+ def self.entries(path, &block)
123
+
124
+ file = self.new(path)
125
+ file.dir_entries
126
+ end
127
+
128
+ def to_file_struct_tree
129
+ map = {}
130
+ @dir_entries.each{ |entry|
131
+ map[entry.extension] ||= {}
132
+ map[entry.extension][entry.path] ||= []
133
+ map[entry.extension][entry.path] << entry
134
+ }
135
+ map
136
+ end
137
+
138
+ def to_blob
139
+ @header.directory_length = calc_directory_length
140
+ @@logger.debug "calculated directory length: #{@header.directory_length}"
141
+
142
+ StringIO.open("") do |io|
143
+ io.write([@header.signature].pack("I*"))
144
+ io.write([@header.version].pack("I*"))
145
+ io.write([@header.directory_length].pack("I*"))
146
+
147
+ write_directory(io)
148
+
149
+ io.rewind
150
+ return io.read
151
+ end
152
+ end
153
+
154
+ def extract_to(base_dir)
155
+ @dir_entries.each{ |entry|
156
+ unless entry.path == " "
157
+ path = File.join(base_dir, entry.path)
158
+ FileUtils.mkdir_p path
159
+ end
160
+ file_path = File.join(base_dir, entry.full_path)
161
+ File.write file_path, entry.payload
162
+ }
163
+ end
164
+
165
+ def self.archive(target_dir)
166
+ vpk = VPKFile.new
167
+ vpk.header = VPKHeader.new
168
+ vpk.header.signature = VPKHeader::VPK_SIGNATURE
169
+ vpk.header.version = VPKHeader::VERSION
170
+ vpk.header.directory_length = nil
171
+ vpk.dir_entries = []
172
+
173
+ Find.find(target_dir){ |f|
174
+ next if File.directory?(f)
175
+ entry = VPKDirectoryEntry.new
176
+ entry.full_path = f.gsub(/^#{target_dir}(\/)?/, "")
177
+ entry.payload = File.read(f)
178
+ vpk.dir_entries << entry
179
+ }
180
+ vpk
181
+ end
182
+
183
+ def write_to(path)
184
+ File.write path, to_blob
185
+ end
186
+
187
+ def to_s
188
+ "#<VPKFile: #{self.object_id} @header=#{@header.inspect} @files=#{self.dir_entries.size}>"
189
+ end
190
+
191
+ private
192
+ def calc_directory_length
193
+ len = 0
194
+ to_file_struct_tree.each{ |extension, path_map|
195
+ len += extension.size + 1
196
+ path_map.each{ |path, entries|
197
+ len += path.size + 1
198
+ entries.each{ |entry|
199
+ len += entry.file.size + 1
200
+ len += 18 #preload data分
201
+ }
202
+ len += 1
203
+ }
204
+ len += 1
205
+ }
206
+ len += 1
207
+ end
208
+
209
+ def read_string(io)
210
+ string = ""
211
+ while true
212
+ b = io.readbyte
213
+ if b == 0
214
+ return string
215
+ end
216
+ string += b.chr
217
+ end
218
+ end
219
+
220
+ # write null terminate string
221
+ def write_string(io, string)
222
+ io.write string
223
+ io.write "\x00"
224
+ end
225
+
226
+ def read_directory(io)
227
+ dir_entries = []
228
+ while true
229
+ extension = read_string(io)
230
+ @@logger.debug "extension = #{extension.inspect}"
231
+ break if extension == ""
232
+
233
+ while true
234
+ path = read_string(io)
235
+ @@logger.debug "path = #{path.inspect}"
236
+ break if path == ""
237
+
238
+ while true
239
+ file = read_string(io)
240
+ @@logger.debug "file = #{file.inspect}"
241
+ break if file == ""
242
+
243
+ dir_entry = read_file_info_and_preload_data(io)
244
+ dir_entry.extension = extension
245
+ dir_entry.path = path
246
+ dir_entry.file = file
247
+ dir_entries << dir_entry
248
+ end
249
+ end
250
+ end
251
+ dir_entries
252
+ end
253
+
254
+ def write_directory(io)
255
+ end_of_header = io.pos
256
+
257
+ total_offset = 0
258
+ to_file_struct_tree.each{ |extension, path_map|
259
+ write_string(io, extension)
260
+ path_map.each{ |path, entries|
261
+ @@logger.debug "write path: #{path.inspect}"
262
+ write_string(io, path)
263
+ entries.each{ |entry|
264
+ write_string(io, entry.file)
265
+
266
+ entry.archive_index = 0x7fff
267
+ entry.terminator = 0xffff
268
+ entry.preload_bytes = 0
269
+ entry.entry_length = entry.payload.size
270
+ entry.entry_offset = total_offset
271
+ total_offset += entry.entry_length
272
+ write_file_info_and_preload(io, entry)
273
+
274
+ pos = io.pos
275
+ io.seek(end_of_header + @header.directory_length + entry.entry_offset)
276
+ io.write(entry.payload)
277
+ io.seek(pos)
278
+ }
279
+ io.write "\x00"
280
+ }
281
+ io.write "\x00"
282
+ }
283
+ io.write "\x00"
284
+ end
285
+
286
+ def read_file_info_and_preload_data(io)
287
+ entry = VPKDirectoryEntry.new
288
+ entry.crc = io.read(4).unpack("V*").first
289
+ entry.preload_bytes = io.read(2).unpack("v*").first
290
+ entry.archive_index = io.read(2).unpack("v*").first
291
+ entry.entry_offset = io.read(4).unpack("v*").first
292
+ entry.entry_length = io.read(4).unpack("v*").first
293
+ entry.terminator = io.read(2).unpack("v*").first
294
+ @@logger.debug "read entry_offset: #{entry.entry_offset}, entry_length: #{entry.entry_length}"
295
+ entry
296
+ end
297
+
298
+ def write_file_info_and_preload(io, entry)
299
+ entry.crc = Zlib.crc32(entry.payload)
300
+ io.write [entry.crc].pack("i*")
301
+ io.write [entry.preload_bytes].pack("S*")
302
+ io.write [entry.archive_index].pack("S*")
303
+ io.write [entry.entry_offset].pack("I*")
304
+ io.write [entry.entry_length].pack("I*")
305
+ io.write [entry.terminator].pack("S*")
306
+ @@logger.debug "write entry_offset: #{entry.entry_offset}, entry_length: #{entry.entry_length}"
307
+ entry
308
+ end
309
+ end
310
+
311
+ module VPKUtil
312
+ def self.extract(in_vpk_path, output_path)
313
+ VPKFile.new(in_vpk_path).extract_to(output_path)
314
+ end
315
+
316
+ def self.archive(dir_path, out_vpk_path)
317
+ VPKFile.archive(dir_path).write_to(out_vpk_path)
318
+ end
319
+ end
320
+
321
+ class VPKError < StandardError; end
322
+ class VPKFileFormatError < VPKError; end
323
+ class VPKFileInvalidCRCError < VPKError; end
324
+ end
@@ -0,0 +1,3 @@
1
+ module VPK
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/vpk/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["kimoto"]
6
+ gem.email = ["sub+peerler@gmail.com"]
7
+ gem.description = %q{VPK File Format Parser (extract and archive)}
8
+ gem.summary = %q{VPK File Format Parser (extract and archive)}
9
+ gem.homepage = "http://github.com/kimoto/vpk"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "vpk"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = VPK::VERSION
17
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vpk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - kimoto
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-13 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: VPK File Format Parser (extract and archive)
15
+ email:
16
+ - sub+peerler@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - README.md
24
+ - Rakefile
25
+ - lib/vpk.rb
26
+ - lib/vpk/version.rb
27
+ - vpk.gemspec
28
+ homepage: http://github.com/kimoto/vpk
29
+ licenses: []
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 1.8.24
49
+ signing_key:
50
+ specification_version: 3
51
+ summary: VPK File Format Parser (extract and archive)
52
+ test_files: []