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