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.
- checksums.yaml +7 -0
- data/bin/pakspy +66 -0
- data/lib/pakspy.rb +230 -0
- 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: []
|