steam_codec 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +21 -0
- data/.yardopts +1 -0
- data/Gemfile +9 -0
- data/README.md +90 -0
- data/Rakefile +13 -0
- data/UNLICENSE +24 -0
- data/lib/steam_codec.rb +5 -0
- data/lib/steam_codec/ACF.rb +176 -0
- data/lib/steam_codec/key_values.rb +116 -0
- data/lib/steam_codec/value_array.rb +45 -0
- data/lib/steam_codec/vdf.rb +26 -0
- data/lib/steam_codec/version.rb +3 -0
- data/spec/acf_spec.rb +137 -0
- data/spec/key_values_spec.rb +179 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/vdf_spec.rb +136 -0
- data/steam_codec.gemspec +27 -0
- metadata +159 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a20d0abedea3bf35bc7450fb8574604122eb1eb0
|
4
|
+
data.tar.gz: 49112685b3c267451119b966912abe90fafd0e5f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 04de7885b1804cd714e7e4e662b47628593f2b32af300d863f05856d6dbb24049cb7f0ced3c60e534a1c900c6bcae024dafa834f8dc8ac1e1518418c29fdb489
|
7
|
+
data.tar.gz: b0057fcefcf85748fe4d3fa4767eb3d44b05407727206b8a70e9e88c31dcd343179d1bf48d0ad595b9a75791211405ba73c3733255aa7302732d81132a142a14
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.0.0
|
4
|
+
- 1.9.3
|
5
|
+
- jruby-19mode
|
6
|
+
- rbx-19mode
|
7
|
+
- ruby-head
|
8
|
+
- jruby-head
|
9
|
+
jdk:
|
10
|
+
- oraclejdk7
|
11
|
+
- openjdk7
|
12
|
+
matrix:
|
13
|
+
exclude:
|
14
|
+
- rvm: 2.0.0
|
15
|
+
jdk: openjdk7
|
16
|
+
- rvm: 1.9.3
|
17
|
+
jdk: openjdk7
|
18
|
+
- rvm: rbx-19mode
|
19
|
+
jdk: openjdk7
|
20
|
+
- rvm: ruby-head
|
21
|
+
jdk: openjdk7
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# SteamCodec
|
2
|
+
|
3
|
+
SteamCodec is a library for working with different [Steam client](http://store.steampowered.com/about/) (and [Source engine](http://source.valvesoftware.com/)) file formats.
|
4
|
+
|
5
|
+
Currently supported formats:
|
6
|
+
|
7
|
+
* [KeyValues](https://developer.valvesoftware.com/wiki/KeyValues)
|
8
|
+
* VDF (Valve Data Format)
|
9
|
+
* ACF (ApplicationCacheFile)
|
10
|
+
|
11
|
+
|
12
|
+
PKV (packed KeyValues) isn't supported yet.
|
13
|
+
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'steam_codec'
|
21
|
+
```
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
```shell
|
26
|
+
bundle
|
27
|
+
```
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
|
31
|
+
```shell
|
32
|
+
gem install SteamCodec
|
33
|
+
```
|
34
|
+
|
35
|
+
### Dependencies
|
36
|
+
|
37
|
+
gem `insensitive_hash`
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'steam_codec'
|
43
|
+
|
44
|
+
File.open("appmanifest_220.acf") do |file|
|
45
|
+
acf = SteamCodec::ACF::loadFromFile(file)
|
46
|
+
puts acf.UserConfig.Name
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
## Documentation
|
51
|
+
|
52
|
+
YARD with markdown is used for documentation (`redcarpet` required)
|
53
|
+
|
54
|
+
## Specs
|
55
|
+
|
56
|
+
RSpec and simplecov are required, to run tests just `rake spec`
|
57
|
+
code coverage will also be generated
|
58
|
+
|
59
|
+
## Code status
|
60
|
+
|
61
|
+
[![Build Status](https://travis-ci.org/davispuh/SteamCodec.png?branch=master)](https://travis-ci.org/davispuh/SteamCodec)
|
62
|
+
[![Dependency Status](https://gemnasium.com/davispuh/SteamCodec.png)](https://gemnasium.com/davispuh/SteamCodec)
|
63
|
+
[![Coverage Status](https://coveralls.io/repos/davispuh/SteamCodec/badge.png)](https://coveralls.io/r/davispuh/SteamCodec)
|
64
|
+
|
65
|
+
## Unlicense
|
66
|
+
|
67
|
+
![Copyright-Free](http://unlicense.org/pd-icon.png)
|
68
|
+
|
69
|
+
All text, documentation, code and files in this repository are in public domain (including this text, README).
|
70
|
+
It means you can copy, modify, distribute and include in your own work/code, even for commercial purposes, all without asking permission.
|
71
|
+
|
72
|
+
[About Unlicense](http://unlicense.org/)
|
73
|
+
|
74
|
+
## Contributing
|
75
|
+
|
76
|
+
Feel free to improve anything.
|
77
|
+
|
78
|
+
1. Fork it
|
79
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
80
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
81
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
82
|
+
5. Create new Pull Request
|
83
|
+
|
84
|
+
|
85
|
+
**Warning**: By sending pull request to this repository you dedicate any and all copyright interest in pull request (code files and all other) to the public domain. (files will be in public domain even if pull request doesn't get merged)
|
86
|
+
|
87
|
+
Also before sending pull request you acknowledge that you own all copyrights or have authorization to dedicate them to public domain.
|
88
|
+
|
89
|
+
If you don't want to dedicate code to public domain or if you're not allowed to (eg. you don't own required copyrights) then DON'T send pull request.
|
90
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'yard'
|
4
|
+
|
5
|
+
desc 'Default: run specs.'
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
desc 'Run specs'
|
9
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
10
|
+
end
|
11
|
+
|
12
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
13
|
+
end
|
data/UNLICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
4
|
+
distribute this software, either in source code form or as a compiled
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
6
|
+
means.
|
7
|
+
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
9
|
+
of this software dedicate any and all copyright interest in the
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
11
|
+
of the public at large and to the detriment of our heirs and
|
12
|
+
successors. We intend this dedication to be an overt act of
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
14
|
+
software under copyright law.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
For more information, please refer to <http://unlicense.org/>
|
data/lib/steam_codec.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
module SteamCodec
|
2
|
+
class ACF
|
3
|
+
|
4
|
+
# More about AppID => https://developer.valvesoftware.com/wiki/Steam_Application_IDs
|
5
|
+
attr_accessor :AppID
|
6
|
+
attr_accessor :Universe
|
7
|
+
attr_accessor :StateFlags
|
8
|
+
attr_accessor :InstallDir
|
9
|
+
attr_accessor :LastUpdated
|
10
|
+
attr_accessor :UpdateResult
|
11
|
+
attr_accessor :SizeOnDisk
|
12
|
+
attr_accessor :BuildID
|
13
|
+
attr_accessor :LastOwner
|
14
|
+
attr_accessor :BytesToDownload
|
15
|
+
attr_accessor :BytesDownloaded
|
16
|
+
attr_accessor :FullValidateOnNextUpdate
|
17
|
+
attr_reader :UserConfig
|
18
|
+
attr_reader :MountedDepots
|
19
|
+
attr_reader :SharedDepots
|
20
|
+
attr_reader :CheckGuid
|
21
|
+
attr_reader :InstallScripts
|
22
|
+
def self.loadFromFile(file)
|
23
|
+
acf = KeyValues::loadFromFile(file)
|
24
|
+
return self.new(acf.AppState) if acf and acf.key?(:AppState)
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.load(data)
|
29
|
+
acf = KeyValues::load(data)
|
30
|
+
return self.new(acf.AppState) if acf and acf.key?(:AppState)
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(appState = nil)
|
35
|
+
load(appState || KeyValues.new)
|
36
|
+
end
|
37
|
+
|
38
|
+
def load(appState)
|
39
|
+
raise ArgumentError, "AppState must be instance of KeyValues" unless appState.is_a?(KeyValues)
|
40
|
+
@AppState = appState
|
41
|
+
@AppID = @AppState.AppID.to_i if @AppState.key?(:AppID)
|
42
|
+
@Universe = @AppState.Universe.to_i if @AppState.key?(:Universe)
|
43
|
+
@StateFlags = @AppState.StateFlags.to_i if @AppState.key?(:StateFlags)
|
44
|
+
@InstallDir = @AppState.InstallDir if @AppState.key?(:InstallDir)
|
45
|
+
@LastUpdated = @AppState.LastUpdated.to_i if @AppState.key?(:LastUpdated)
|
46
|
+
@UpdateResult = @AppState.UpdateResult.to_i if @AppState.key?(:UpdateResult)
|
47
|
+
@SizeOnDisk = @AppState.SizeOnDisk.to_i if @AppState.key?(:SizeOnDisk)
|
48
|
+
@BuildID = @AppState.BuildID.to_i if @AppState.key?(:BuildID)
|
49
|
+
@LastOwner = @AppState.LastOwner if @AppState.key?(:LastOwner)
|
50
|
+
@BytesToDownload = @AppState.BytesToDownload.to_i if @AppState.key?(:BytesToDownload)
|
51
|
+
@BytesDownloaded = @AppState.BytesDownloaded.to_i if @AppState.key?(:BytesDownloaded)
|
52
|
+
@FullValidateOnNextUpdate = !@AppState.FullValidateOnNextUpdate.to_i.zero? if @AppState.key?(:FullValidateOnNextUpdate)
|
53
|
+
userConfig = nil
|
54
|
+
mountedDepots = {}
|
55
|
+
sharedDepots = {}
|
56
|
+
checkGuid = {}
|
57
|
+
installScripts = {}
|
58
|
+
userConfig = @AppState.UserConfig if @AppState.key?(:UserConfig)
|
59
|
+
mountedDepots = @AppState.MountedDepots if @AppState.key?(:MountedDepots)
|
60
|
+
sharedDepots = @AppState.SharedDepots if @AppState.key?(:sharedDepots)
|
61
|
+
checkGuid = @AppState.CheckGuid if @AppState.key?(:CheckGuid)
|
62
|
+
installScripts = @AppState.InstallScripts if @AppState.key?(:InstallScripts)
|
63
|
+
@UserConfig = UserConfig.new(userConfig)
|
64
|
+
@MountedDepots = MountedDepots.new(mountedDepots)
|
65
|
+
@SharedDepots = SharedDepots.new(sharedDepots)
|
66
|
+
@InstallScripts = InstallScripts.new(installScripts)
|
67
|
+
end
|
68
|
+
|
69
|
+
class UserConfig
|
70
|
+
attr_accessor :Name
|
71
|
+
attr_accessor :GameID
|
72
|
+
attr_accessor :Installed
|
73
|
+
attr_accessor :AppInstallDir
|
74
|
+
attr_accessor :Language
|
75
|
+
attr_accessor :BetaKey
|
76
|
+
def initialize(userConfig = nil)
|
77
|
+
load(userConfig || KeyValues.new)
|
78
|
+
end
|
79
|
+
|
80
|
+
def load(userConfig)
|
81
|
+
raise ArgumentError, "UserConfig must be instance of KeyValues" unless userConfig.is_a?(KeyValues)
|
82
|
+
@UserConfig = userConfig
|
83
|
+
@Name = @UserConfig.name if @UserConfig.key?(:name)
|
84
|
+
@GameID = @UserConfig.gameid.to_i if @UserConfig.key?(:gameid)
|
85
|
+
@Installed = !@UserConfig.installdir.to_i.zero? if @UserConfig.key?(:installdir)
|
86
|
+
@AppInstallDir = @UserConfig.appinstalldir if @UserConfig.key?(:appinstalldir)
|
87
|
+
@Language = @UserConfig.language if @UserConfig.key?(:language)
|
88
|
+
@BetaKey = @UserConfig.BetaKey if @UserConfig.key?(:BetaKey)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class MountedDepots
|
93
|
+
def initialize(mountedDepots = {})
|
94
|
+
load(mountedDepots)
|
95
|
+
end
|
96
|
+
|
97
|
+
def load(mountedDepots)
|
98
|
+
raise ArgumentError, "MountedDepots must be instance of Hash" unless mountedDepots.is_a?(Hash)
|
99
|
+
@MountedDepots = {}
|
100
|
+
mountedDepots.each do |depot, manifest|
|
101
|
+
@MountedDepots[depot.to_i] = manifest
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def depots
|
106
|
+
@MountedDepots.keys
|
107
|
+
end
|
108
|
+
|
109
|
+
def manifests
|
110
|
+
@MountedDepots.values
|
111
|
+
end
|
112
|
+
|
113
|
+
def getManifest(depotID)
|
114
|
+
@MountedDepots.each do |depot, manifest|
|
115
|
+
return manifest if depot == depotID
|
116
|
+
end
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
|
120
|
+
def getDepot(manifestID)
|
121
|
+
@MountedDepots.each do |depot, manifest|
|
122
|
+
return depot if manifest == manifestID
|
123
|
+
end
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
|
127
|
+
def set(depot, manifest)
|
128
|
+
@MountedDepots[depot] = manifest
|
129
|
+
end
|
130
|
+
|
131
|
+
def remove(depot)
|
132
|
+
@MountedDepots.delete(depot)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class SharedDepots
|
137
|
+
attr_reader :Depots
|
138
|
+
def initialize(sharedDepots = {})
|
139
|
+
load(sharedDepots)
|
140
|
+
end
|
141
|
+
|
142
|
+
def load(sharedDepots)
|
143
|
+
raise ArgumentError, "SharedDepots must be instance of Hash" unless sharedDepots.is_a?(Hash)
|
144
|
+
@SharedDepots = {}
|
145
|
+
sharedDepots.each do |depot, baseDepot|
|
146
|
+
@SharedDepots[depot.to_i] = baseDepot.to_i
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def depots
|
151
|
+
@SharedDepots.keys
|
152
|
+
end
|
153
|
+
|
154
|
+
def getDepot(depotID)
|
155
|
+
@SharedDepots.each do |depot, baseDepot|
|
156
|
+
return baseDepot if depot == depotID
|
157
|
+
end
|
158
|
+
nil
|
159
|
+
end
|
160
|
+
|
161
|
+
def set(depot, baseDepot)
|
162
|
+
@SharedDepots[depot] = baseDepot
|
163
|
+
end
|
164
|
+
|
165
|
+
def remove(depot)
|
166
|
+
@SharedDepots.delete(depot)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
class CheckGuid < ValueArray
|
171
|
+
end
|
172
|
+
|
173
|
+
class InstallScripts < ValueArray
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'insensitive_hash/minimal'
|
3
|
+
|
4
|
+
module SteamCodec
|
5
|
+
# About KeyValues => https://developer.valvesoftware.com/wiki/KeyValues
|
6
|
+
# Valve's implementation => https://github.com/ValveSoftware/source-sdk-2013/blob/master/mp/src/tier1/KeyValues.cpp
|
7
|
+
# SteamKit's implementation => https://github.com/SteamRE/SteamKit/blob/master/SteamKit2/SteamKit2/Types/KeyValue.cs
|
8
|
+
class KeyValues < InsensitiveHash
|
9
|
+
class Parser
|
10
|
+
def self.proccess(data, last = true)
|
11
|
+
token = /"[^"]*"/
|
12
|
+
data = data.gsub(/(?<=^|[\s{}])(\s*)([^"{}\s\n\r]+)(\s*)(?=[\s{}]|\z)/, '\1"\2"\3')
|
13
|
+
if last
|
14
|
+
data.gsub!(/(#{token}:\s*#{token}|})(?=\s*")/, '\1,')
|
15
|
+
data.gsub!(/(#{token})(?=\s*{|[ \t\f]+")/, '\1:')
|
16
|
+
else
|
17
|
+
data.gsub!(/(#{token}:\s*#{token}|})(?=\s*"|\s*\z)/, '\1,')
|
18
|
+
data.gsub!(/(#{token})(?=\s*{|[ \t\f]+"|\s*\z)/, '\1:')
|
19
|
+
end
|
20
|
+
data
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.isEscaped(data, index, char = '\\')
|
24
|
+
return false if index == 0
|
25
|
+
escaped = false
|
26
|
+
(index - 1).downto(0) do |num|
|
27
|
+
if data[num] == char
|
28
|
+
escaped = !escaped
|
29
|
+
else
|
30
|
+
break
|
31
|
+
end
|
32
|
+
end
|
33
|
+
escaped
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.getQuoteList(data)
|
37
|
+
indexes = []
|
38
|
+
quoted = false
|
39
|
+
length = data.length
|
40
|
+
length.times do |index|
|
41
|
+
if data[index] == '"'
|
42
|
+
escaped = false
|
43
|
+
escaped = self.isEscaped(data, index) if quoted
|
44
|
+
if not escaped
|
45
|
+
indexes << index
|
46
|
+
quoted = !quoted
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
raise RuntimeError, "Unmatched quotes" if quoted
|
51
|
+
indexes
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.toJSON(data)
|
55
|
+
raise ArgumentError, "data must be String" unless data.is_a?(String)
|
56
|
+
str = ''
|
57
|
+
previous = 0
|
58
|
+
data.gsub!(/^#(include|base).*$/, '') # include and base not supported so just ignore
|
59
|
+
quoteList = self.getQuoteList(data)
|
60
|
+
quoteList.each_index do |index|
|
61
|
+
quoted = index % 2 == 1
|
62
|
+
part = data[previous...quoteList[index]]
|
63
|
+
previous = quoteList[index] + 1
|
64
|
+
next if part.empty? and not quoted
|
65
|
+
if quoted
|
66
|
+
str += '"' + part.gsub(/\n/,'\n') + '"'
|
67
|
+
lastIndex = data.length
|
68
|
+
lastIndex = quoteList[index + 1] if index + 1 < quoteList.length
|
69
|
+
nextPart = data[previous...lastIndex].gsub(/\\\\.*$/,'') # remove comments
|
70
|
+
nextPart = self.proccess(nextPart, true)
|
71
|
+
case nextPart
|
72
|
+
when /\A(\s*{|[ \t]+\z)/
|
73
|
+
str += ':'
|
74
|
+
when /\A\s+\z/
|
75
|
+
str += ','
|
76
|
+
end
|
77
|
+
else
|
78
|
+
part = part.gsub(/\\\\[^\n]*$/,'') # remove comments
|
79
|
+
str += self.proccess(part, index + 1 >= quoteList.length)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
lastIndex = data.length
|
83
|
+
part = data[previous...lastIndex]
|
84
|
+
str += self.proccess(part, true) if part
|
85
|
+
'{' + str + '}'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.loadFromJSON(json)
|
90
|
+
JSON.parse(json, {:object_class => self})
|
91
|
+
rescue JSON::ParserError
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.loadFromFile(file)
|
96
|
+
raise ArgumentError, "file must respond to :read" unless file.respond_to?(:read)
|
97
|
+
json = self::Parser::toJSON(file.read)
|
98
|
+
self.loadFromJSON(json)
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.load(data)
|
102
|
+
json = self::Parser::toJSON(data)
|
103
|
+
self.loadFromJSON(json)
|
104
|
+
end
|
105
|
+
|
106
|
+
def method_missing(name, *args, &block) # :nodoc:
|
107
|
+
return self[name] if args.empty? and block.nil? and key?(name)
|
108
|
+
super
|
109
|
+
end
|
110
|
+
|
111
|
+
def respond_to_missing?(name, include_private = false) # :nodoc:
|
112
|
+
return true if key?(name)
|
113
|
+
super
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module SteamCodec
|
2
|
+
class ValueArray
|
3
|
+
def initialize(valueHash = {})
|
4
|
+
load(valueHash)
|
5
|
+
end
|
6
|
+
|
7
|
+
def load(valueHash)
|
8
|
+
raise ArgumentError, "ValueHash must be instance of Hash" unless valueHash.is_a?(Hash)
|
9
|
+
@ValueHash = {}
|
10
|
+
valueHash.each do |id, file|
|
11
|
+
@ValueHash[id.to_i] = file
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](id)
|
16
|
+
@ValueHash[id]
|
17
|
+
end
|
18
|
+
|
19
|
+
def []=(id, file)
|
20
|
+
@ValueHash[id] = file
|
21
|
+
end
|
22
|
+
|
23
|
+
def add(file)
|
24
|
+
id = @ValueHash.keys.max + 1
|
25
|
+
@ValueHash[id] = file
|
26
|
+
id
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove(id)
|
30
|
+
@ValueHash.delete(id)
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_a
|
34
|
+
check = []
|
35
|
+
@ValueHash.sort_by { |key, value| key.to_s.to_i }.each do |array|
|
36
|
+
check << array.last
|
37
|
+
end
|
38
|
+
check
|
39
|
+
end
|
40
|
+
|
41
|
+
alias_method :all, :to_a
|
42
|
+
alias_method :get, :[]
|
43
|
+
alias_method :set, :[]=
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module SteamCodec
|
2
|
+
class VDF < KeyValues
|
3
|
+
# About VDF => http://wiki.teamfortress.com/wiki/WebAPI/VDF
|
4
|
+
def self.loadFromFile(file)
|
5
|
+
vdf = KeyValues::loadFromFile(file)
|
6
|
+
return self[vdf] if vdf
|
7
|
+
nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.load(data)
|
11
|
+
vdf = KeyValues::load(data)
|
12
|
+
return self[vdf] if vdf
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def isSignaturesValid?(publicKey)
|
17
|
+
return true unless key?("kvsignatures")
|
18
|
+
self["kvsignatures"].each do |key, signature|
|
19
|
+
# TODO
|
20
|
+
return false
|
21
|
+
end
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/spec/acf_spec.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SteamCodec::ACF do
|
4
|
+
|
5
|
+
let(:appCacheFile) { <<-EOS
|
6
|
+
"AppState"
|
7
|
+
{
|
8
|
+
"appid" "207930"
|
9
|
+
"Universe" "1"
|
10
|
+
"StateFlags" "4"
|
11
|
+
"installdir" "sacred_citadel"
|
12
|
+
"LastUpdated" "1377100000"
|
13
|
+
"UpdateResult" "0"
|
14
|
+
"SizeOnDisk" "1193761500"
|
15
|
+
"buildid" "60250"
|
16
|
+
"LastOwner" "0"
|
17
|
+
"BytesToDownload" "0"
|
18
|
+
"BytesDownloaded" "0"
|
19
|
+
"FullValidateOnNextUpdate" "1"
|
20
|
+
"UserConfig"
|
21
|
+
{
|
22
|
+
"name" "Sacred Citadel"
|
23
|
+
"gameid" "207930"
|
24
|
+
"installed" "1"
|
25
|
+
"appinstalldir" "C:\\Steam\\steamapps\\common\\sacred_citadel"
|
26
|
+
"language" "english"
|
27
|
+
"BetaKey" "public"
|
28
|
+
}
|
29
|
+
"MountedDepots"
|
30
|
+
{
|
31
|
+
"228982" "6039653574073929574"
|
32
|
+
"228983" "4721092052804263356"
|
33
|
+
}
|
34
|
+
"SharedDepots"
|
35
|
+
{
|
36
|
+
"228984" "228980"
|
37
|
+
}
|
38
|
+
"checkguid"
|
39
|
+
{
|
40
|
+
"0" "Bin\\some.exe"
|
41
|
+
"1" "Bin\\another.exe"
|
42
|
+
}
|
43
|
+
"InstallScripts"
|
44
|
+
{
|
45
|
+
"0" "_CommonRedist\\vcredist\\2008\\installscript.vdf"
|
46
|
+
"1" "_CommonRedist\\vcredist\\2010\\installscript.vdf"
|
47
|
+
}
|
48
|
+
}
|
49
|
+
EOS
|
50
|
+
}
|
51
|
+
|
52
|
+
describe '.loadFromFile' do
|
53
|
+
it 'should successfully load from file' do
|
54
|
+
StringIO.open(appCacheFile) do |file|
|
55
|
+
SteamCodec::ACF::loadFromFile(file).should be_an_instance_of SteamCodec::ACF
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '.load' do
|
61
|
+
it 'should successfully load' do
|
62
|
+
SteamCodec::ACF::load(appCacheFile).should be_an_instance_of SteamCodec::ACF
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '.new' do
|
67
|
+
it 'should initialize new instance with empty fields' do
|
68
|
+
SteamCodec::ACF.new.AppID.should be_nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '#InstallDir' do
|
73
|
+
it 'should return value' do
|
74
|
+
SteamCodec::ACF::load(appCacheFile).InstallDir.should eq("sacred_citadel")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe SteamCodec::ACF::UserConfig do
|
79
|
+
describe '.new' do
|
80
|
+
it 'should initialize new instance with empty fields' do
|
81
|
+
SteamCodec::ACF::UserConfig.new.Name.should be_nil
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should initialize new instance with provided fields' do
|
85
|
+
SteamCodec::ACF::UserConfig.new(SteamCodec::KeyValues[{"Name" => "Game"}]).Name.should eq("Game")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe SteamCodec::ACF::MountedDepots do
|
91
|
+
let(:depots) { {"228982" => "6039653574073929574"} }
|
92
|
+
describe '.new' do
|
93
|
+
it 'should initialize new instance with empty fields' do
|
94
|
+
SteamCodec::ACF::MountedDepots.new.depots.should eq([])
|
95
|
+
SteamCodec::ACF::MountedDepots.new.manifests.should eq([])
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should initialize new instance with provided fields' do
|
99
|
+
SteamCodec::ACF::MountedDepots.new(depots).depots.should eq([228982])
|
100
|
+
SteamCodec::ACF::MountedDepots.new(depots).manifests.should eq(["6039653574073929574"])
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '#getManifest' do
|
105
|
+
it 'should return manifest' do
|
106
|
+
SteamCodec::ACF::MountedDepots.new(depots).getManifest(228982).should eq("6039653574073929574")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe '#getDepot' do
|
111
|
+
it 'should return depot' do
|
112
|
+
SteamCodec::ACF::MountedDepots.new(depots).getDepot("6039653574073929574").should eq(228982)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe SteamCodec::ACF::SharedDepots do
|
118
|
+
let(:depots) { {"228984" => "228980"} }
|
119
|
+
describe '.new' do
|
120
|
+
it 'should initialize new instance with empty fields' do
|
121
|
+
SteamCodec::ACF::SharedDepots.new.depots.should eq([])
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should initialize new instance with provided fields' do
|
125
|
+
SteamCodec::ACF::SharedDepots.new(depots).depots.should eq([228984])
|
126
|
+
SteamCodec::ACF::SharedDepots.new(depots).getDepot(228984).should eq(228980)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe '#getDepot' do
|
131
|
+
it 'should return depot' do
|
132
|
+
SteamCodec::ACF::SharedDepots.new(depots).getDepot(228984).should eq(228980)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SteamCodec::KeyValues do
|
4
|
+
|
5
|
+
let(:tokenKey) { 'TokenKey123' }
|
6
|
+
let(:tokenValue) { 'Token+Value4567' }
|
7
|
+
let(:tokenKeyQuoted) { "\"#{tokenKey}\"" }
|
8
|
+
let(:tokenValueQuoted) { "\"#{tokenValue}\"" }
|
9
|
+
|
10
|
+
let(:jsonSample1a) { "#{tokenKeyQuoted} #{tokenValueQuoted}" }
|
11
|
+
let(:jsonSample1b) { "#{tokenKeyQuoted}\n{\t#{tokenKeyQuoted}\t#{tokenValueQuoted}\n}" }
|
12
|
+
|
13
|
+
let(:jsonSample2a) { "#{tokenKeyQuoted} #{tokenValueQuoted}\n#{tokenKeyQuoted}\t#{tokenValueQuoted}" }
|
14
|
+
let(:jsonSample2b) { "#{tokenKeyQuoted}\t{}\n#{tokenKeyQuoted} #{tokenValueQuoted}" }
|
15
|
+
|
16
|
+
let(:jsonSample3a) { '"Ke\\ty\\\\is" "Val\\nue\\\\\\""' }
|
17
|
+
let(:jsonSample3b) { '"Ke\\\\ty\\is\\\\" "te\\\\s\\\\n\\\\v\\"\\\\"' }
|
18
|
+
|
19
|
+
let(:jsonSample4a) { "#{tokenKey} #{tokenValue}" }
|
20
|
+
let(:jsonSample4b) { "#{tokenKey}\n{\t#{tokenKey}\t#{tokenValue}\n}" }
|
21
|
+
|
22
|
+
let(:jsonSample5a) { 'abc "lol this {weird string}"' }
|
23
|
+
let(:jsonSample5b) { "\"\\\"really weird\\\"\n{\n\\\"string\\\" \\\"this is\\\"\n}\"\n{\n\"string\" \"this is\"\n}" }
|
24
|
+
|
25
|
+
let(:jsonSample6a) { "#{tokenKeyQuoted}\\\\this should be ignored\n{\t#{tokenKeyQuoted}\t#{tokenValueQuoted}\n}" }
|
26
|
+
|
27
|
+
let(:jsonSample7a) { "#base <blah.vdf>\n#{tokenKey}\n{\t#{tokenKey}\t#{tokenValue}\n}" }
|
28
|
+
let(:jsonSample7b) { "#include <blah.vdf>\n#{tokenKey}\n{\t#{tokenKey}\t#{tokenValue}\n}" }
|
29
|
+
|
30
|
+
let(:keyValueData) { <<-EOS
|
31
|
+
"AppState"
|
32
|
+
{
|
33
|
+
"appid" "320"
|
34
|
+
"Universe" "1"
|
35
|
+
}
|
36
|
+
EOS
|
37
|
+
}
|
38
|
+
|
39
|
+
describe SteamCodec::KeyValues::Parser do
|
40
|
+
|
41
|
+
describe '.isEscaped' do
|
42
|
+
it 'empty string should not be escaped' do
|
43
|
+
SteamCodec::KeyValues::Parser::isEscaped('', 0).should be_false
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should be escaped' do
|
47
|
+
SteamCodec::KeyValues::Parser::isEscaped('\\"', 1).should be_true
|
48
|
+
SteamCodec::KeyValues::Parser::isEscaped('\\\\\\"', 3).should be_true
|
49
|
+
SteamCodec::KeyValues::Parser::isEscaped('trol\\lol\\lol\\"trrr', 13).should be_true
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should not be escaped' do
|
53
|
+
SteamCodec::KeyValues::Parser::isEscaped('\\\\"', 2).should be_false
|
54
|
+
SteamCodec::KeyValues::Parser::isEscaped('\\\\\\\\"', 4).should be_false
|
55
|
+
SteamCodec::KeyValues::Parser::isEscaped('trol\\lol\\lol\\\\"trrr', 14).should be_false
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '.toJSON' do
|
61
|
+
it 'should convert correctly empty data' do
|
62
|
+
SteamCodec::KeyValues::Parser::toJSON('').should eq('{}')
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should raise exception on invalid data' do
|
66
|
+
expect { SteamCodec::KeyValues::Parser::toJSON(nil) }.to raise_error(ArgumentError)
|
67
|
+
expect { SteamCodec::KeyValues::Parser::toJSON({}) }.to raise_error(ArgumentError)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should add colon after tokens' do
|
71
|
+
SteamCodec::KeyValues::Parser::toJSON(jsonSample1a).should eq("{#{tokenKeyQuoted}: #{tokenValueQuoted}}")
|
72
|
+
SteamCodec::KeyValues::Parser::toJSON(jsonSample1b).should eq("{#{tokenKeyQuoted}:\n{\t#{tokenKeyQuoted}:\t#{tokenValueQuoted}\n}}")
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should add comma after entries' do
|
76
|
+
SteamCodec::KeyValues::Parser::toJSON(jsonSample2a).should eq("{#{tokenKeyQuoted}: #{tokenValueQuoted},\n#{tokenKeyQuoted}:\t#{tokenValueQuoted}}")
|
77
|
+
SteamCodec::KeyValues::Parser::toJSON(jsonSample2b).should eq("{#{tokenKeyQuoted}:\t{},\n#{tokenKeyQuoted}: #{tokenValueQuoted}}")
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should regard slashes' do
|
81
|
+
SteamCodec::KeyValues::Parser::toJSON(jsonSample3a).should eq('{"Ke\\ty\\\\is": "Val\\nue\\\\\\""}')
|
82
|
+
SteamCodec::KeyValues::Parser::toJSON(jsonSample3b).should eq('{"Ke\\\\ty\\is\\\\": "te\\\\s\\\\n\\\\v\\"\\\\"}')
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should parse keys and values without quotes' do
|
86
|
+
SteamCodec::KeyValues::Parser::toJSON(jsonSample4a).should eq("{#{tokenKeyQuoted}: #{tokenValueQuoted}}")
|
87
|
+
SteamCodec::KeyValues::Parser::toJSON(jsonSample4b).should eq("{#{tokenKeyQuoted}:\n{\t#{tokenKeyQuoted}:\t#{tokenValueQuoted}\n}}")
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should parse special cases' do
|
91
|
+
SteamCodec::KeyValues::Parser::toJSON(jsonSample5a).should eq("{\"abc\": \"lol this {weird string}\"}")
|
92
|
+
SteamCodec::KeyValues::Parser::toJSON(jsonSample5b).should eq("{\"\\\"really weird\\\"\\n{\\n\\\"string\\\" \\\"this is\\\"\\n}\":\n{\n\"string\": \"this is\"\n}}")
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should ignore comments' do
|
96
|
+
SteamCodec::KeyValues::Parser::toJSON(jsonSample6a).should eq("{#{tokenKeyQuoted}:\n{\t#{tokenKeyQuoted}:\t#{tokenValueQuoted}\n}}")
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should ignore #include and #base' do
|
100
|
+
SteamCodec::KeyValues::Parser::toJSON(jsonSample7a).should eq("{\n#{tokenKeyQuoted}:\n{\t#{tokenKeyQuoted}:\t#{tokenValueQuoted}\n}}")
|
101
|
+
SteamCodec::KeyValues::Parser::toJSON(jsonSample7b).should eq("{\n#{tokenKeyQuoted}:\n{\t#{tokenKeyQuoted}:\t#{tokenValueQuoted}\n}}")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe '.loadFromJSON' do
|
107
|
+
it 'should return nil for invalid json' do
|
108
|
+
SteamCodec::KeyValues::loadFromJSON('invalid :P').should be_nil
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe '.load' do
|
113
|
+
it 'should load correctly empty data' do
|
114
|
+
SteamCodec::KeyValues::load('').should eq({})
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should raise exception on invalid data' do
|
118
|
+
expect { SteamCodec::KeyValues::load(nil) }.to raise_error(ArgumentError)
|
119
|
+
expect { SteamCodec::KeyValues::load({}) }.to raise_error(ArgumentError)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should load correctly various representations' do
|
123
|
+
SteamCodec::KeyValues::load(jsonSample1a).should eq({ tokenKey => tokenValue })
|
124
|
+
SteamCodec::KeyValues::load(jsonSample1b).should eq({ tokenKey => { tokenKey => tokenValue } })
|
125
|
+
SteamCodec::KeyValues::load(jsonSample2a).should eq({ tokenKey => tokenValue })
|
126
|
+
SteamCodec::KeyValues::load(jsonSample2b).should eq({ tokenKey => tokenValue })
|
127
|
+
SteamCodec::KeyValues::load(jsonSample3a).should eq({"Ke\ty\\is" => "Val\nue\\\""})
|
128
|
+
SteamCodec::KeyValues::load(jsonSample3b).should eq({'Ke\\tyis\\' => "te\\s\\n\\v\"\\"})
|
129
|
+
SteamCodec::KeyValues::load(jsonSample4a).should eq({ tokenKey => tokenValue })
|
130
|
+
SteamCodec::KeyValues::load(jsonSample4b).should eq({ tokenKey => { tokenKey => tokenValue } })
|
131
|
+
SteamCodec::KeyValues::load(jsonSample5a).should eq({"abc" => "lol this {weird string}"})
|
132
|
+
SteamCodec::KeyValues::load(jsonSample5b).should eq({"\"really weird\"\n{\n\"string\" \"this is\"\n}" => {"string" => "this is"}})
|
133
|
+
SteamCodec::KeyValues::load(jsonSample6a).should eq({ tokenKey => { tokenKey => tokenValue } })
|
134
|
+
SteamCodec::KeyValues::load(jsonSample7a).should eq({ tokenKey => { tokenKey => tokenValue } })
|
135
|
+
SteamCodec::KeyValues::load(jsonSample7b).should eq({ tokenKey => { tokenKey => tokenValue } })
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe '.loadFromFile' do
|
140
|
+
it 'should raise exception if not file' do
|
141
|
+
expect { SteamCodec::KeyValues::loadFromFile("blah") }.to raise_error(ArgumentError)
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should succesfully load from file' do
|
145
|
+
StringIO.open(keyValueData) do |file|
|
146
|
+
SteamCodec::KeyValues::loadFromFile(file).should eq({ "AppState" => { "appid" => "320", "Universe" => "1" } })
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'should be KeyValues instance' do
|
151
|
+
StringIO.open(keyValueData) do |file|
|
152
|
+
SteamCodec::KeyValues::loadFromFile(file).should be_an_instance_of SteamCodec::KeyValues
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe 'access any field' do
|
158
|
+
let(:keyValues) { SteamCodec::KeyValues::load(keyValueData) }
|
159
|
+
it 'should be able to access AppState' do
|
160
|
+
keyValues.AppState.should eq({ "appid" => "320", "Universe" => "1" })
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'should be able to read AppID' do
|
164
|
+
keyValues.AppState.AppID.should eq("320")
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'should check if field exists' do
|
168
|
+
keyValues.AppState.has_key?(:AppID).should be_true
|
169
|
+
keyValues.AppState.has_key?(:UserConfig).should be_false
|
170
|
+
keyValues.respond_to?(:appstate).should be_true
|
171
|
+
keyValues.respond_to?(:nope).should be_false
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'should raise exception for non-existent field' do
|
175
|
+
expect { keyValues.AppState.UserConfig }.to raise_error(NoMethodError)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/vdf_spec.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SteamCodec::VDF do
|
4
|
+
|
5
|
+
let(:valveDataFile) { <<-EOS
|
6
|
+
"InstallScript"
|
7
|
+
{
|
8
|
+
"Registry"
|
9
|
+
{
|
10
|
+
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Activision\\Singularity"
|
11
|
+
{
|
12
|
+
"string"
|
13
|
+
{
|
14
|
+
"EXEString" "%INSTALLDIR%\\Binaries\\Singularity.exe"
|
15
|
+
"InstallPath" "%INSTALLDIR%"
|
16
|
+
"IntVersion" "35.0"
|
17
|
+
"Version" "1.0"
|
18
|
+
"english"
|
19
|
+
{
|
20
|
+
"Language" "1033"
|
21
|
+
"LanguageCode" "ENU"
|
22
|
+
"Localization" "ENU"
|
23
|
+
}
|
24
|
+
"french"
|
25
|
+
{
|
26
|
+
"Language" "1036"
|
27
|
+
"LanguageCode" "FRA"
|
28
|
+
"Localization" "FRA"
|
29
|
+
}
|
30
|
+
"german"
|
31
|
+
{
|
32
|
+
"Language" "1031"
|
33
|
+
"LanguageCode" "DEU"
|
34
|
+
"Localization" "DEU"
|
35
|
+
}
|
36
|
+
"italian"
|
37
|
+
{
|
38
|
+
"Language" "1040"
|
39
|
+
"LanguageCode" "ITA"
|
40
|
+
"Localization" "ITA"
|
41
|
+
}
|
42
|
+
"spanish"
|
43
|
+
{
|
44
|
+
"Language" "1034"
|
45
|
+
"LanguageCode" "ESP"
|
46
|
+
"Localization" "ESP"
|
47
|
+
}
|
48
|
+
}
|
49
|
+
"dword"
|
50
|
+
{
|
51
|
+
"english"
|
52
|
+
{
|
53
|
+
"GameLanguage" "0"
|
54
|
+
}
|
55
|
+
"french"
|
56
|
+
{
|
57
|
+
"GameLanguage" "1"
|
58
|
+
}
|
59
|
+
"german"
|
60
|
+
{
|
61
|
+
"GameLanguage" "0"
|
62
|
+
}
|
63
|
+
"italian"
|
64
|
+
{
|
65
|
+
"GameLanguage" "2"
|
66
|
+
}
|
67
|
+
"spanish"
|
68
|
+
{
|
69
|
+
"GameLanguage" "4"
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
"Run Process"
|
75
|
+
{
|
76
|
+
"DirectX"
|
77
|
+
{
|
78
|
+
"process 1" "%INSTALLDIR%\\redist\\DirectX\\DXSETUP.exe"
|
79
|
+
"command 1" "/silent"
|
80
|
+
"Description" "DirectX Installation"
|
81
|
+
"NoCleanUp" "1"
|
82
|
+
}
|
83
|
+
"VC++"
|
84
|
+
{
|
85
|
+
"process 1" "%INSTALLDIR%\\redist\\vcredist_x86.exe"
|
86
|
+
"command 1" "/q:a"
|
87
|
+
"process 2" "%INSTALLDIR%\\redist\\vcredist_x86_2005.exe"
|
88
|
+
"command 2" "/q:a"
|
89
|
+
"Description" "VC++ Redist Installation"
|
90
|
+
"NoCleanUp" "1"
|
91
|
+
}
|
92
|
+
"PhysX Version"
|
93
|
+
{
|
94
|
+
"HasRunKey" "HKEY_LOCAL_MACHINE\\SOFTWARE\\AGEIA Technologies"
|
95
|
+
"process 1" "%INSTALLDIR%\\redist\\PhysX_9.09.1112_SystemSoftware.exe"
|
96
|
+
"command 1" "/quiet"
|
97
|
+
"NoCleanUp" "1"
|
98
|
+
"MinimumHasRunValue" "9091112"
|
99
|
+
}
|
100
|
+
}
|
101
|
+
}
|
102
|
+
"kvsignatures"
|
103
|
+
{
|
104
|
+
"InstallScript" "973f7d89125ad1fc782a970deb175cf84d48c49581ba3a76fedfefa8116a6d500459fd8df99285850ef99d767a83c2de009de5951607930557c937439908fbc1cc8156e963320c54080e7a1a634ccbf2e5f58883bd11bb2ed10c07be5af8e482d9a17d22a6fe40980124e5fced1c241f158b460941984c41432eeeb2aeaa01b1"
|
105
|
+
}
|
106
|
+
EOS
|
107
|
+
}
|
108
|
+
|
109
|
+
describe '.loadFromFile' do
|
110
|
+
it 'should successfully load from file' do
|
111
|
+
StringIO.open(valveDataFile) do |file|
|
112
|
+
SteamCodec::VDF::loadFromFile(file).should be_an_instance_of SteamCodec::VDF
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe '.load' do
|
118
|
+
it 'should successfully load' do
|
119
|
+
SteamCodec::VDF::load(valveDataFile).should be_an_instance_of SteamCodec::VDF
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe 'key field' do
|
124
|
+
it 'should return value' do
|
125
|
+
SteamCodec::VDF::load(valveDataFile).InstallScript.Registry.should be_an_instance_of SteamCodec::KeyValues
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe '#isSignaturesValid?' do
|
130
|
+
it 'should check if signature is valid' do
|
131
|
+
SteamCodec::VDF.new.isSignaturesValid?(nil).should be_true
|
132
|
+
SteamCodec::VDF::load(valveDataFile).isSignaturesValid?(nil).should be_false
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
data/steam_codec.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'steam_codec/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'steam_codec'
|
8
|
+
spec.version = SteamCodec::VERSION
|
9
|
+
spec.authors = ['Dāvis']
|
10
|
+
spec.email = ['davispuh@gmail.com']
|
11
|
+
spec.description = 'Load, parse and manage various Steam client, Source Engine file formats. For example VDF and ACF'
|
12
|
+
spec.summary = 'Library for working with different Steam client (and Source engine) file formats.'
|
13
|
+
spec.homepage = 'https://github.com/davispuh/SteamCodec'
|
14
|
+
spec.license = 'UNLICENSE'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_runtime_dependency 'insensitive_hash', '~> 0.3', '>= 0.3.3'
|
22
|
+
spec.add_development_dependency 'bundler'
|
23
|
+
spec.add_development_dependency 'rake'
|
24
|
+
spec.add_development_dependency 'rspec'
|
25
|
+
spec.add_development_dependency 'yard'
|
26
|
+
spec.add_development_dependency 'simplecov'
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: steam_codec
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dāvis
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-09-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: insensitive_hash
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.3'
|
20
|
+
- - '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.3.3
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.3'
|
30
|
+
- - '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.3.3
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: bundler
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rspec
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: yard
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: simplecov
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
description: Load, parse and manage various Steam client, Source Engine file formats.
|
104
|
+
For example VDF and ACF
|
105
|
+
email:
|
106
|
+
- davispuh@gmail.com
|
107
|
+
executables: []
|
108
|
+
extensions: []
|
109
|
+
extra_rdoc_files: []
|
110
|
+
files:
|
111
|
+
- .gitignore
|
112
|
+
- .travis.yml
|
113
|
+
- .yardopts
|
114
|
+
- Gemfile
|
115
|
+
- README.md
|
116
|
+
- Rakefile
|
117
|
+
- UNLICENSE
|
118
|
+
- lib/steam_codec.rb
|
119
|
+
- lib/steam_codec/ACF.rb
|
120
|
+
- lib/steam_codec/key_values.rb
|
121
|
+
- lib/steam_codec/value_array.rb
|
122
|
+
- lib/steam_codec/vdf.rb
|
123
|
+
- lib/steam_codec/version.rb
|
124
|
+
- spec/acf_spec.rb
|
125
|
+
- spec/key_values_spec.rb
|
126
|
+
- spec/spec_helper.rb
|
127
|
+
- spec/vdf_spec.rb
|
128
|
+
- steam_codec.gemspec
|
129
|
+
homepage: https://github.com/davispuh/SteamCodec
|
130
|
+
licenses:
|
131
|
+
- UNLICENSE
|
132
|
+
metadata: {}
|
133
|
+
post_install_message:
|
134
|
+
rdoc_options: []
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - '>='
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
requirements: []
|
148
|
+
rubyforge_project:
|
149
|
+
rubygems_version: 2.0.7
|
150
|
+
signing_key:
|
151
|
+
specification_version: 4
|
152
|
+
summary: Library for working with different Steam client (and Source engine) file
|
153
|
+
formats.
|
154
|
+
test_files:
|
155
|
+
- spec/acf_spec.rb
|
156
|
+
- spec/key_values_spec.rb
|
157
|
+
- spec/spec_helper.rb
|
158
|
+
- spec/vdf_spec.rb
|
159
|
+
has_rdoc:
|