vpk 0.0.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,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: []