pakspy 0.1.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 (4) hide show
  1. checksums.yaml +7 -0
  2. data/bin/pakspy +66 -0
  3. data/lib/pakspy.rb +230 -0
  4. metadata +45 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b396c939066b3794ad12095be2c3008c7c0c20b2b134c4d75a5dbf94d1775e94
4
+ data.tar.gz: 963338f9731f59c1a7f0ea51ebb716dbaf2d189084c6463556c9a58ea753bb37
5
+ SHA512:
6
+ metadata.gz: d49070223f57749f0550e1ea83e6766614de10cc604bcc0bb7b8611ea83f1c1f09fd050d4f0b2891adc28b993cad89efaeffb070d785109fd92498c4cad34553
7
+ data.tar.gz: 29bd18461565800c335a6711bc28bf3a3f5861abce20436e87884b5c3a440117994274217089a9e9c0e46ad8c057c9f05d6e6a2674e48af11d2759627ed5e70d
data/bin/pakspy ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+ require 'pakspy'
3
+
4
+ def print_usage
5
+ puts "Usage:"
6
+ puts "pakspy list <pak>"
7
+ puts " Lists contents of a PAK file"
8
+ puts "pakspy dump <pak> <directory-to>"
9
+ puts " Extracts a PAK file to a directory"
10
+ puts "pakspy extract <pak> <file-in-pak> <file-on-fs>"
11
+ puts " Extracts a single file from PAK to file system"
12
+ puts "pakspy create <pak> <directory-from>"
13
+ puts " Creates a PAK from a directory"
14
+ puts "pakspy insert <pak> <file-on-fs> <file-in-pak>"
15
+ puts " Inserts a file in existing PAK"
16
+ end
17
+
18
+ if ARGV.length > 1
19
+ command, *arguments = ARGV
20
+
21
+ case command
22
+ when "list"
23
+ if arguments.length == 1
24
+ pak = PAKFile.new arguments[0]
25
+ pak.list.each do |name|
26
+ puts name
27
+ end
28
+ else
29
+ print_usage
30
+ end
31
+ when "dump"
32
+ if arguments.length == 2
33
+ pak = PAKFile.new arguments[0]
34
+ pak.extract_all arguments[1]
35
+ else
36
+ print_usage
37
+ end
38
+ when "extract"
39
+ if arguments.length == 3
40
+ pak = PAKFile.new arguments[0]
41
+ pak.extract arguments[1], arguments[2]
42
+ else
43
+ print_usage
44
+ end
45
+ when "create"
46
+ if arguments.length == 2
47
+ pak = PAKFile.new
48
+ pak.insert_all arguments[1]
49
+ pak.save arguments[0]
50
+ else
51
+ print_usage
52
+ end
53
+ when "insert"
54
+ if arguments.length == 3
55
+ pak = PAKFile.new arguments[0]
56
+ pak.insert arguments[1], arguments[2]
57
+ pak.save arguments[0]
58
+ else
59
+ print_usage
60
+ end
61
+ else
62
+ print_usage
63
+ end
64
+ else
65
+ print_usage
66
+ end
data/lib/pakspy.rb ADDED
@@ -0,0 +1,230 @@
1
+ ##
2
+ # Class handles Quake style PAK files
3
+ class PAKFile
4
+ ##
5
+ # Opens an existing PAK file at +path+ or if not specified creates a virtual PAK
6
+ def initialize(path = nil)
7
+ @file_hash = Hash.new
8
+ @pak_file = nil
9
+
10
+ unless path.nil?
11
+ @pak_file = File.open path, "rb"
12
+
13
+ header = Header.new @pak_file.read 12
14
+ puts "Warning: Might not be a real PAK" unless header.magic == "PACK"
15
+ file_count = header.size / 64
16
+
17
+ @pak_file.seek header.offset
18
+ file_count.times do
19
+ entry = FileEntryPAK.new self, @pak_file.read(64)
20
+ file_add entry
21
+ end
22
+ end
23
+ end
24
+
25
+ def finalize
26
+ @pak_file.close unless @pak_file.nil?
27
+ end
28
+
29
+ ##
30
+ # Extracts a file +name+ from this PAKFile to +path+ on system
31
+ def extract(name, path)
32
+ file_entry = file_find(name)
33
+ raise ArgumentError, "No such file #{name} in this PAK." if file_entry.nil?
34
+
35
+ # Create the necessary directories
36
+ path_current = ""
37
+ File.dirname(path).split(/[\/\\]/).each do |dir|
38
+ path_current += dir
39
+ Dir.mkdir path_current unless Dir.exists?(path_current)
40
+ path_current += "/"
41
+ end
42
+
43
+ # Transfer contents
44
+ file = File.open path, "wb"
45
+ file.write file_entry.read
46
+
47
+ file.close
48
+ end
49
+
50
+ ##
51
+ # Extracts all files in PAK to +dir+ directory
52
+ def extract_all(dir)
53
+ files_list.each do |file_name|
54
+ extract file_name, dir + "/" + file_name
55
+ end
56
+ end
57
+
58
+ ##
59
+ # Inserts a system file at +path+ as +name+
60
+ def insert(path, name)
61
+ file_add FileEntrySystem.new self, path, name
62
+ end
63
+
64
+ ##
65
+ # Inserts a directory +dir+ recursively contents as their names
66
+ def insert_all(dir)
67
+ insert_all_helper(dir, "")
68
+ end
69
+
70
+ ##
71
+ # Saves all the changes to +path+
72
+ def save(path)
73
+ tmp_path = "#{path}.tmp_"
74
+ file = File.open tmp_path, "wb"
75
+
76
+ file_entries = Array.new
77
+
78
+ # Will finish header later when we know where the file entries will be
79
+ file.write "PACK"
80
+ file.seek 12
81
+
82
+ # Insert all of the files
83
+ files_list.each do |name|
84
+ entry = file_find name
85
+
86
+ file_entry = Hash.new
87
+ file_entry[:name] = entry.name
88
+ file_entry[:offset] = file.pos
89
+
90
+ file.write entry.read
91
+
92
+ file_entry[:size] = file.pos - file_entry[:offset]
93
+ file_entries.push file_entry
94
+ end
95
+
96
+ # Now we know the rest of the info needed for the header
97
+ file_entry_pos = file.pos
98
+ file.seek 4
99
+ file.write [file_entry_pos, file_entries.length * 64].pack("VV")
100
+
101
+ # And finally add the file entries
102
+ file.seek file_entry_pos
103
+ file_entries.each do |file_entry|
104
+ file.write [file_entry[:name], file_entry[:offset], file_entry[:size]].pack("a56VV")
105
+ end
106
+
107
+ # close so we can open it again
108
+ file.close
109
+
110
+ # And finally move it to the correct place
111
+ File.rename tmp_path, path
112
+ end
113
+
114
+ ##
115
+ # Lists all files in PAK to an array
116
+ def list
117
+ files_list
118
+ end
119
+
120
+ attr_reader :pak_file
121
+
122
+ private
123
+
124
+ ##
125
+ # Adds +file_entry+ to pak
126
+ def file_add(file_entry)
127
+ @file_hash[file_entry.name] = file_entry
128
+ end
129
+
130
+ ##
131
+ # Finds file called +name+ in pak
132
+ def file_find(name)
133
+ @file_hash[name]
134
+ end
135
+
136
+ ##
137
+ # Returns a list of all the files as an array of strings
138
+ def files_list
139
+ @file_hash.values.map { |entry| entry.name }
140
+ end
141
+
142
+ def insert_all_helper(dir, prefix)
143
+ Dir.entries(dir).each do |entry|
144
+ unless entry == "." or entry == ".."
145
+ if File.directory? "#{dir}/#{entry}"
146
+ insert_all_helper("#{dir}/#{entry}", "#{prefix}#{entry}/")
147
+ else
148
+ insert("#{dir}/#{entry}", "#{prefix}#{entry}")
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ ##
155
+ # Reperesents PAK header block
156
+ class Header
157
+ attr_accessor :magic, :offset, :size
158
+
159
+ ##
160
+ # Unpacks the 12 header bytes from +entry+
161
+ def initialize(header)
162
+ # magic - 4 byte arbitary string (not null terminated)
163
+ # offset - 4 byte integer (little endian)
164
+ # size - 4 byte integer (little endian)
165
+ data = header.unpack "a4VV"
166
+
167
+ @magic = data[0]
168
+ @offset = data[1]
169
+ @size = data[2]
170
+ end
171
+ end
172
+
173
+ ##
174
+ # Base class for all files handled by PAKFile
175
+ class FileEntry
176
+ attr_accessor :name
177
+
178
+ ##
179
+ # +pack+ has to be PAKFile that owns this entry
180
+ def initialize(pack)
181
+ @pack = pack
182
+ @name = ""
183
+ end
184
+
185
+ def read
186
+ puts "This is just a base class..."
187
+ ""
188
+ end
189
+ end
190
+
191
+ ##
192
+ # File located inside the PAK itself
193
+ class FileEntryPAK < FileEntry
194
+ def initialize(pack, entry)
195
+ super pack
196
+
197
+ # name - max 58 byte null terminated string
198
+ # offset - 4 byte integer (little endian)
199
+ # size - 4 byte integer (little endian)
200
+ data = entry.unpack "a56VV"
201
+
202
+ @name = data[0].rstrip
203
+ @offset = data[1]
204
+ @size = data[2]
205
+ end
206
+
207
+ def read
208
+ @pack.pak_file.seek @offset
209
+ @pack.pak_file.read @size
210
+ end
211
+ end
212
+
213
+ ##
214
+ # File located on a file system, not in the pack
215
+ class FileEntrySystem < FileEntry
216
+ def initialize(pack, path, name)
217
+ super pack
218
+ @path = path
219
+ @name = name
220
+ end
221
+
222
+ def read
223
+ f = File.open @path, "rb"
224
+ data = f.read
225
+ f.close
226
+ data
227
+ end
228
+ end
229
+ end
230
+
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pakspy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Valters Mednis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-09-02 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email: valters.mednis@gmail.com
15
+ executables:
16
+ - pakspy
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - bin/pakspy
21
+ - lib/pakspy.rb
22
+ homepage: https://github.com/vmednis/pakspy
23
+ licenses:
24
+ - MIT
25
+ metadata: {}
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubygems_version: 3.0.6
42
+ signing_key:
43
+ specification_version: 4
44
+ summary: Tools for working with Quake style PAK files.
45
+ test_files: []