1 // -*- Mode: C++; tab-width:2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 // vi:tw=80:et:ts=2:sts=2
3 //
4 // -----------------------------------------------------------------------
5 //
6 // This file is part of libreallive, a dependency of RLVM.
7 //
8 // -----------------------------------------------------------------------
9 //
10 // Copyright (c) 2006, 2007 Peter Jolly
11 // Copyright (c) 2007 Elliot Glaysher
12 //
13 // Permission is hereby granted, free of charge, to any person
14 // obtaining a copy of this software and associated documentation
15 // files (the "Software"), to deal in the Software without
16 // restriction, including without limitation the rights to use, copy,
17 // modify, merge, publish, distribute, sublicense, and/or sell copies
18 // of the Software, and to permit persons to whom the Software is
19 // furnished to do so, subject to the following conditions:
20 //
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 //
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
28 // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
29 // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
30 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31 // SOFTWARE.
32 //
33 // -----------------------------------------------------------------------
34
35 #include "libreallive/gameexe.h"
36
37 #include <boost/tokenizer.hpp>
38 #include <boost/algorithm/string.hpp>
39 #include <boost/filesystem/fstream.hpp>
40
41 #include <iostream>
42 #include <fstream>
43 #include <iomanip>
44
45 #include "libreallive/defs.h"
46
47 namespace fs = boost::filesystem;
48
49 #define is_space(c) (c == '\r' || c == '\n' || c == ' ' || c == '\t')
50 #define is_num(c) (c == '-' || (c >= '0' && c <= '9'))
51 #define is_data(c) (c == '"' || is_num(c))
52
53 // A boost::TokenizerFunction used to extract valid pieces of data
54 // from the value part of a gameexe key/value pair.
55 class gameexe_token_extractor {
56 public:
reset()57 void reset() {}
58
59 template <typename InputIterator, typename Token>
operator ()(InputIterator & next,InputIterator end,Token & tok)60 bool operator()(InputIterator& next, InputIterator end, Token& tok) {
61 tok = Token();
62 // Advance to the next data character
63 for (; next != end && (!is_data(*next)); ++next) {}
64
65 if (next == end)
66 return false;
67
68 if (*next == '"') {
69 tok += '"';
70 next++;
71 for (; next != end && *next != '"'; ++next)
72 tok += *next;
73 tok += '"';
74 next++;
75 } else {
76 char lastChar = '\0';
77
78 // Eat the current character and all
79 while (next != end) {
80 if (*next == '-') {
81 // Dashes are ambiguous. They are both seperators and the negative
82 // sign and we have to tokenize differently based on what it's
83 // doing. If the previous character is a number, we are being used as
84 // a range separator.
85 if (lastChar >= '0' && lastChar <= '9') {
86 // Skip the dash so we don't treat the next number as negative.
87 next++;
88 break;
89 } else {
90 // Consume the dash for the next parts.
91 tok += *next;
92 }
93 } else if (is_num(*next)) {
94 // All other numbers are consumed here.
95 tok += *next;
96 } else {
97 // We only deal with numbers in this branch.
98 break;
99 }
100
101 lastChar = *next;
102 ++next;
103 }
104 }
105
106 return true;
107 }
108 };
109
110 // -----------------------------------------------------------------------
111
Gameexe()112 Gameexe::Gameexe() {}
113
114 // -----------------------------------------------------------------------
115
Gameexe(const fs::path & gameexefile)116 Gameexe::Gameexe(const fs::path& gameexefile) : data_(), cdata_() {
117 fs::ifstream ifs(gameexefile);
118 if (!ifs) {
119 std::ostringstream oss;
120 oss << "Could not find Gameexe.ini file! (Looking in " << gameexefile
121 << ")";
122 throw libreallive::Error(oss.str());
123 }
124
125 std::string line;
126 while (std::getline(ifs, line)) {
127 parseLine(line);
128 }
129 }
130
~Gameexe()131 Gameexe::~Gameexe() {}
132
133 // -----------------------------------------------------------------------
134
parseLine(const std::string & line)135 void Gameexe::parseLine(const std::string& line) {
136 size_t firstHash = line.find_first_of('#');
137 if (firstHash != std::string::npos) {
138 // Extract what's the key and value
139 size_t firstEqual = line.find_first_of('=');
140 std::string key = line.substr(firstHash + 1, firstEqual - firstHash - 1);
141 std::string value = line.substr(firstEqual + 1);
142
143 // Get rid of extra whitespace
144 boost::trim(key);
145 boost::trim(value);
146
147 Gameexe_vec_type vec;
148
149 // Extract all numeric and data values from the value
150 typedef boost::tokenizer<gameexe_token_extractor> ValueTokenizer;
151 ValueTokenizer tokenizer(value);
152 for (const std::string& tok : tokenizer) {
153 if (tok[0] == '"') {
154 std::string unquoted = tok.substr(1, tok.size() - 2);
155 cdata_.push_back(unquoted);
156 vec.push_back(cdata_.size() - 1);
157 } else if (tok != "-") {
158 try {
159 vec.push_back(std::stoi(tok));
160 }
161 catch (...) {
162 std::cerr << "Couldn't int-ify '" << tok << "'" << std::endl;
163 vec.push_back(0);
164 }
165 }
166 }
167 data_.emplace(key, vec);
168 }
169 }
170
171 // -----------------------------------------------------------------------
172
GetIntArray(GameexeData_t::const_iterator key)173 const std::vector<int>& Gameexe::GetIntArray(
174 GameexeData_t::const_iterator key) {
175 if (key == data_.end()) {
176 static std::vector<int> falseVector;
177 return falseVector;
178 }
179
180 return key->second;
181 }
182
183 // -----------------------------------------------------------------------
184
GetIntAt(GameexeData_t::const_iterator key,int index)185 int Gameexe::GetIntAt(GameexeData_t::const_iterator key, int index) {
186 if (key == data_.end())
187 ThrowUnknownKey("TMP");
188
189 return key->second.at(index);
190 }
191
192 // -----------------------------------------------------------------------
193
Exists(const std::string & key)194 bool Gameexe::Exists(const std::string& key) {
195 return data_.find(key) != data_.end();
196 }
197
198 // -----------------------------------------------------------------------
199
GetStringAt(GameexeData_t::const_iterator key,int index)200 std::string Gameexe::GetStringAt(GameexeData_t::const_iterator key, int index) {
201 int cindex = GetIntAt(key, index);
202 return cdata_.at(cindex);
203 }
204
205 // -----------------------------------------------------------------------
206
SetStringAt(const std::string & key,const std::string & value)207 void Gameexe::SetStringAt(const std::string& key, const std::string& value) {
208 Gameexe_vec_type toStore;
209 cdata_.push_back(value);
210 toStore.push_back(cdata_.size() - 1);
211 data_.erase(key);
212 data_.emplace(key, toStore);
213 }
214
215 // -----------------------------------------------------------------------
216
SetIntAt(const std::string & key,const int value)217 void Gameexe::SetIntAt(const std::string& key, const int value) {
218 Gameexe_vec_type toStore;
219 toStore.push_back(value);
220 data_.erase(key);
221 data_.emplace(key, toStore);
222 }
223
224 // -----------------------------------------------------------------------
225
Find(const std::string & key)226 GameexeData_t::const_iterator Gameexe::Find(const std::string& key) {
227 return data_.find(key);
228 }
229
230 // -----------------------------------------------------------------------
231
AddToStream(const std::string & x,std::ostringstream & ss)232 void Gameexe::AddToStream(const std::string& x, std::ostringstream& ss) {
233 ss << x;
234 }
235
236 // -----------------------------------------------------------------------
237
AddToStream(const int & x,std::ostringstream & ss)238 void Gameexe::AddToStream(const int& x, std::ostringstream& ss) {
239 ss << std::setw(3) << std::setfill('0') << x;
240 }
241
242 // -----------------------------------------------------------------------
243
ThrowUnknownKey(const std::string & key)244 void Gameexe::ThrowUnknownKey(const std::string& key) {
245 std::ostringstream ss;
246 ss << "Unknown Gameexe key '" << key << "'";
247 throw libreallive::Error(ss.str());
248 }
249
250 // -----------------------------------------------------------------------
251
filtering_begin(const std::string & filter)252 GameexeFilteringIterator Gameexe::filtering_begin(const std::string& filter) {
253 return GameexeFilteringIterator(filter, *this, data_.begin());
254 }
255
256 // -----------------------------------------------------------------------
257
filtering_end()258 GameexeFilteringIterator Gameexe::filtering_end() {
259 return GameexeFilteringIterator("", *this, data_.end());
260 }
261
262 // -----------------------------------------------------------------------
263 // GameexeInterpretObject
264 // -----------------------------------------------------------------------
GameexeInterpretObject(const std::string & key,Gameexe & objectToLookupOn)265 GameexeInterpretObject::GameexeInterpretObject(const std::string& key,
266 Gameexe& objectToLookupOn)
267 : key_(key),
268 iterator_(objectToLookupOn.Find(key)),
269 object_to_lookup_on_(objectToLookupOn) {}
270
271 // -----------------------------------------------------------------------
272
GameexeInterpretObject(const std::string & key,GameexeData_t::const_iterator it,Gameexe & objectToLookupOn)273 GameexeInterpretObject::GameexeInterpretObject(const std::string& key,
274 GameexeData_t::const_iterator it,
275 Gameexe& objectToLookupOn)
276 : key_(key), iterator_(it), object_to_lookup_on_(objectToLookupOn) {}
277
278 // -----------------------------------------------------------------------
279
~GameexeInterpretObject()280 GameexeInterpretObject::~GameexeInterpretObject() {}
281
282 // -----------------------------------------------------------------------
283
ToInt(const int defaultValue) const284 const int GameexeInterpretObject::ToInt(const int defaultValue) const {
285 const std::vector<int>& ints = object_to_lookup_on_.GetIntArray(iterator_);
286 if (ints.size() == 0)
287 return defaultValue;
288
289 return ints[0];
290 }
291
292 // -----------------------------------------------------------------------
293
ToInt() const294 const int GameexeInterpretObject::ToInt() const {
295 const std::vector<int>& ints = object_to_lookup_on_.GetIntArray(iterator_);
296 if (ints.size() == 0)
297 object_to_lookup_on_.ThrowUnknownKey(key_);
298
299 return ints[0];
300 }
301
302 // -----------------------------------------------------------------------
303
GetIntAt(int index) const304 int GameexeInterpretObject::GetIntAt(int index) const {
305 return object_to_lookup_on_.GetIntAt(iterator_, index);
306 }
307
308 // -----------------------------------------------------------------------
309
ToString(const std::string & defaultValue) const310 const std::string GameexeInterpretObject::ToString(
311 const std::string& defaultValue) const {
312 try {
313 return object_to_lookup_on_.GetStringAt(iterator_, 0);
314 }
315 catch (...) {
316 return defaultValue;
317 }
318 }
319
320 // -----------------------------------------------------------------------
321
ToString() const322 const std::string GameexeInterpretObject::ToString() const {
323 try {
324 return object_to_lookup_on_.GetStringAt(iterator_, 0);
325 }
326 catch (...) {
327 object_to_lookup_on_.ThrowUnknownKey(key_);
328 }
329
330 // Shut the -Wall up
331 return "";
332 }
333
334 // -----------------------------------------------------------------------
335
GetStringAt(int index) const336 const std::string GameexeInterpretObject::GetStringAt(int index) const {
337 return object_to_lookup_on_.GetStringAt(iterator_, index);
338 }
339
340 // -----------------------------------------------------------------------
341
ToIntVector() const342 const std::vector<int>& GameexeInterpretObject::ToIntVector() const {
343 const std::vector<int>& ints = object_to_lookup_on_.GetIntArray(iterator_);
344 if (ints.size() == 0)
345 object_to_lookup_on_.ThrowUnknownKey(key_);
346
347 return ints;
348 }
349
350 // -----------------------------------------------------------------------
351
Exists() const352 bool GameexeInterpretObject::Exists() const {
353 return object_to_lookup_on_.Exists(key_);
354 }
355
356 // -----------------------------------------------------------------------
357
GetKeyParts() const358 const std::vector<std::string> GameexeInterpretObject::GetKeyParts() const {
359 std::vector<std::string> keyparts;
360 boost::split(keyparts, key_, boost::is_any_of("."));
361 return keyparts;
362 }
363
364 // -----------------------------------------------------------------------
365
operator =(const std::string & value)366 GameexeInterpretObject& GameexeInterpretObject::operator=(
367 const std::string& value) {
368 // Set the key to incoming int
369 object_to_lookup_on_.SetStringAt(key_, value);
370 return *this;
371 }
372
373 // -----------------------------------------------------------------------
374
operator =(const int value)375 GameexeInterpretObject& GameexeInterpretObject::operator=(const int value) {
376 // Set the key to incoming int
377 object_to_lookup_on_.SetIntAt(key_, value);
378 return *this;
379 }
380
381 // -----------------------------------------------------------------------
382 // GameexeFilteringIterator
383 // -----------------------------------------------------------------------
384
incrementUntilValid()385 void GameexeFilteringIterator::incrementUntilValid() {
386 while (currentKey != gexe.data_.end() &&
387 !boost::istarts_with(currentKey->first, filterKeys)) {
388 currentKey++;
389 }
390 }
391