steam_codec 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
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
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development, :test do
4
+ gem 'redcarpet', :platforms => [:ruby, :mswin, :mingw]
5
+ gem 'coveralls', require: false if ENV['CI']
6
+ end
7
+
8
+ # Specify your gem's dependencies in SteamCodec.gemspec
9
+ gemspec
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/>
@@ -0,0 +1,5 @@
1
+ require "steam_codec/version"
2
+ require "steam_codec/key_values"
3
+ require "steam_codec/value_array"
4
+ require "steam_codec/vdf"
5
+ require "steam_codec/acf"
@@ -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
@@ -0,0 +1,3 @@
1
+ module SteamCodec
2
+ VERSION = "0.0.1"
3
+ 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
@@ -0,0 +1,4 @@
1
+ require 'simplecov'
2
+
3
+ SimpleCov.start
4
+ require_relative '../lib/steam_codec'
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
@@ -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: