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 RLVM, a RealLive virtual machine clone.
7 //
8 // -----------------------------------------------------------------------
9 //
10 // Copyright (C) 2006, 2007 Elliot Glaysher
11 //
12 // This program is free software; you can redistribute it and/or modify
13 // it under the terms of the GNU General Public License as published by
14 // the Free Software Foundation; either version 3 of the License, or
15 // (at your option) any later version.
16 //
17 // This program is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 // GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
25 //
26 // -----------------------------------------------------------------------
27 
28 #include "systems/base/system.h"
29 
30 #include <boost/algorithm/string.hpp>
31 #include <boost/filesystem/convenience.hpp>
32 #include <boost/filesystem/operations.hpp>
33 #include <boost/filesystem/path.hpp>
34 
35 #include <algorithm>
36 #include <fstream>
37 #include <iomanip>
38 #include <iostream>
39 #include <string>
40 #include <utility>
41 #include <vector>
42 
43 #include "libreallive/gameexe.h"
44 #include "long_operations/load_game_long_operation.h"
45 #include "machine/long_operation.h"
46 #include "machine/rlmachine.h"
47 #include "machine/serialization.h"
48 #include "modules/module_sys.h"
49 #include "systems/base/event_system.h"
50 #include "systems/base/graphics_system.h"
51 #include "systems/base/platform.h"
52 #include "systems/base/rlvm_info.h"
53 #include "systems/base/sound_system.h"
54 #include "systems/base/system_error.h"
55 #include "systems/base/text_system.h"
56 #include "utilities/exception.h"
57 #include "utilities/string_utilities.h"
58 
59 using boost::replace_all;
60 using boost::to_lower;
61 
62 namespace fs = boost::filesystem;
63 
64 namespace {
65 
66 const std::vector<std::string> ALL_FILETYPES = {"g00", "pdt", "anm", "gan",
67                                                 "hik", "wav", "ogg", "nwa",
68                                                 "mp3", "ovk", "koe", "nwk"};
69 
70 struct LoadingGameFromStream : public LoadGameLongOperation {
LoadingGameFromStream__anon338ac01c0111::LoadingGameFromStream71   LoadingGameFromStream(RLMachine& machine,
72                         const std::shared_ptr<std::stringstream>& selection)
73       : LoadGameLongOperation(machine), selection_(selection) {}
74 
Load__anon338ac01c0111::LoadingGameFromStream75   virtual void Load(RLMachine& machine) override {
76     // We need to copy data here onto the stack because the action of loading
77     // will deallocate this object.
78     std::shared_ptr<std::stringstream> s = selection_;
79     Serialization::loadGameFrom(*s, machine);
80     // Warning: |this| is an invalid pointer now.
81   }
82 
83   std::shared_ptr<std::stringstream> selection_;
84 };
85 
86 }  // namespace
87 
88 // I assume GAN files can't go through the OBJ_FILETYPES path.
89 const std::vector<std::string> OBJ_FILETYPES = {"anm", "g00", "pdt"};
90 const std::vector<std::string> IMAGE_FILETYPES = {"g00", "pdt"};
91 const std::vector<std::string> PDT_IMAGE_FILETYPES = {"pdt"};
92 const std::vector<std::string> GAN_FILETYPES = {"gan"};
93 const std::vector<std::string> ANM_FILETYPES = {"anm"};
94 const std::vector<std::string> HIK_FILETYPES = {"hik", "g00", "pdt"};
95 const std::vector<std::string> SOUND_FILETYPES = {"wav", "ogg", "nwa", "mp3"};
96 const std::vector<std::string> KOE_ARCHIVE_FILETYPES = {"ovk", "koe", "nwk"};
97 const std::vector<std::string> KOE_LOOSE_FILETYPES = {"ogg"};
98 
99 class MenuReseter : public LongOperation {
100  public:
MenuReseter(System & sys)101   explicit MenuReseter(System& sys) : sys_(sys) {}
102 
operator ()(RLMachine & machine)103   bool operator()(RLMachine& machine) {
104     sys_.in_menu_ = false;
105     return true;
106   }
107 
108  private:
109   System& sys_;
110 };
111 
112 // -----------------------------------------------------------------------
113 // SystemGlobals
114 // -----------------------------------------------------------------------
115 
SystemGlobals()116 SystemGlobals::SystemGlobals()
117     : confirm_save_load_(true), low_priority_(false) {}
118 
119 // -----------------------------------------------------------------------
120 // System
121 // -----------------------------------------------------------------------
122 
System()123 System::System()
124     : in_menu_(false),
125       force_fast_forward_(false),
126       force_wait_(false),
127       use_western_font_(false) {
128   std::fill(syscom_status_,
129             syscom_status_ + NUM_SYSCOM_ENTRIES,
130             SYSCOM_VISIBLE);
131 }
132 
~System()133 System::~System() {}
134 
SetPlatform(const std::shared_ptr<Platform> & platform)135 void System::SetPlatform(const std::shared_ptr<Platform>& platform) {
136   platform_ = platform;
137 }
138 
TakeSelectionSnapshot(RLMachine & machine)139 void System::TakeSelectionSnapshot(RLMachine& machine) {
140   previous_selection_.reset(new std::stringstream);
141   Serialization::saveGameTo(*previous_selection_, machine);
142 }
143 
RestoreSelectionSnapshot(RLMachine & machine)144 void System::RestoreSelectionSnapshot(RLMachine& machine) {
145   // We need to reference this on the stack because it will call
146   // System::reset() to get the black screen. (We'll reset again inside
147   // LoadingGameFromStream.)
148   std::shared_ptr<std::stringstream> s = previous_selection_;
149   if (s) {
150     // LoadingGameFromStream adds itself to the callstack of |machine| due to
151     // subtle timing issues.
152     new LoadingGameFromStream(machine, s);
153   }
154 }
155 
IsSyscomEnabled(int syscom)156 int System::IsSyscomEnabled(int syscom) {
157   CheckSyscomIndex(syscom, "System::is_syscom_enabled");
158 
159   // Special cases where state of the interpreter would override the
160   // programmatically set (or user set) values.
161   if (syscom == SYSCOM_SET_SKIP_MODE && !text().kidoku_read()) {
162     // Skip mode should be grayed out when there's no text to read
163     if (syscom_status_[syscom] == SYSCOM_VISIBLE)
164       return SYSCOM_GREYED_OUT;
165   } else if (syscom == SYSCOM_RETURN_TO_PREVIOUS_SELECTION) {
166     if (syscom_status_[syscom] == SYSCOM_VISIBLE)
167       return previous_selection_.get() ? SYSCOM_VISIBLE : SYSCOM_GREYED_OUT;
168   }
169 
170   return syscom_status_[syscom];
171 }
172 
HideSyscom()173 void System::HideSyscom() {
174   std::fill(syscom_status_,
175             syscom_status_ + NUM_SYSCOM_ENTRIES,
176             SYSCOM_INVISIBLE);
177 }
178 
HideSyscomEntry(int syscom)179 void System::HideSyscomEntry(int syscom) {
180   CheckSyscomIndex(syscom, "System::hide_system");
181   syscom_status_[syscom] = SYSCOM_INVISIBLE;
182 }
183 
EnableSyscom()184 void System::EnableSyscom() {
185   std::fill(syscom_status_,
186             syscom_status_ + NUM_SYSCOM_ENTRIES,
187             SYSCOM_VISIBLE);
188 }
189 
EnableSyscomEntry(int syscom)190 void System::EnableSyscomEntry(int syscom) {
191   CheckSyscomIndex(syscom, "System::enable_system");
192   syscom_status_[syscom] = SYSCOM_VISIBLE;
193 }
194 
DisableSyscom()195 void System::DisableSyscom() {
196   std::fill(syscom_status_,
197             syscom_status_ + NUM_SYSCOM_ENTRIES,
198             SYSCOM_GREYED_OUT);
199 }
200 
DisableSyscomEntry(int syscom)201 void System::DisableSyscomEntry(int syscom) {
202   CheckSyscomIndex(syscom, "System::disable_system");
203   syscom_status_[syscom] = SYSCOM_GREYED_OUT;
204 }
205 
ReadSyscom(int syscom)206 int System::ReadSyscom(int syscom) {
207   throw rlvm::Exception("ReadSyscom unimplemented!");
208 }
209 
ShowSyscomMenu(RLMachine & machine)210 void System::ShowSyscomMenu(RLMachine& machine) {
211   Gameexe& gexe = machine.system().gameexe();
212 
213   if (gexe("CANCELCALL_MOD") == 1) {
214     if (!in_menu_) {
215       // Multiple right clicks shouldn't spawn multiple copies of the menu
216       // system on top of each other.
217       in_menu_ = true;
218       machine.PushLongOperation(new MenuReseter(*this));
219 
220       std::vector<int> cancelcall = gexe("CANCELCALL");
221       machine.Farcall(cancelcall.at(0), cancelcall.at(1));
222     }
223   } else if (platform_) {
224     platform_->ShowNativeSyscomMenu(machine);
225   } else {
226     std::cerr << "(We don't deal with non-custom SYSCOM calls yet.)"
227               << std::endl;
228   }
229 }
230 
InvokeSyscom(RLMachine & machine,int syscom)231 void System::InvokeSyscom(RLMachine& machine, int syscom) {
232   switch (syscom) {
233     case SYSCOM_SAVE:
234       InvokeSaveOrLoad(
235           machine, syscom, "SYSTEMCALL_SAVE_MOD", "SYSTEMCALL_SAVE");
236       break;
237     case SYSCOM_LOAD:
238       InvokeSaveOrLoad(
239           machine, syscom, "SYSTEMCALL_LOAD_MOD", "SYSTEMCALL_LOAD");
240       break;
241     case SYSCOM_MESSAGE_SPEED:
242     case SYSCOM_WINDOW_ATTRIBUTES:
243     case SYSCOM_VOLUME_SETTINGS:
244     case SYSCOM_MISCELLANEOUS_SETTINGS:
245     case SYSCOM_VOICE_SETTINGS:
246     case SYSCOM_FONT_SELECTION:
247     case SYSCOM_BGM_FADE:
248     case SYSCOM_BGM_SETTINGS:
249     case SYSCOM_AUTO_MODE_SETTINGS:
250     case SYSCOM_USE_KOE:
251     case SYSCOM_DISPLAY_VERSION: {
252       if (platform_)
253         platform_->InvokeSyscomStandardUI(machine, syscom);
254       break;
255     }
256     case SYSCOM_RETURN_TO_PREVIOUS_SELECTION:
257       RestoreSelectionSnapshot(machine);
258       break;
259     case SYSCOM_SHOW_WEATHER:
260       graphics().set_should_show_weather(!graphics().should_show_weather());
261       break;
262     case SYSCOM_SHOW_OBJECT_1:
263       graphics().set_should_show_object1(!graphics().should_show_object1());
264       break;
265     case SYSCOM_SHOW_OBJECT_2:
266       graphics().set_should_show_object2(!graphics().should_show_object2());
267       break;
268     case SYSCOM_CLASSIFY_TEXT:
269       std::cerr << "We have no idea what classifying text even means!"
270                 << std::endl;
271       break;
272     case SYSCOM_OPEN_MANUAL_PATH:
273       std::cerr << "Opening manual path..." << std::endl;
274       break;
275     case SYSCOM_SET_SKIP_MODE:
276       text().SetSkipMode(!text().skip_mode());
277       break;
278     case SYSCOM_AUTO_MODE:
279       text().SetAutoMode(!text().auto_mode());
280       break;
281     case SYSCOM_MENU_RETURN:
282       // This is a hack since we probably have a bunch of crap on the stack.
283       machine.ClearLongOperationsOffBackOfStack();
284 
285       // Simulate a MenuReturn.
286       Sys_MenuReturn()(machine);
287       break;
288     case SYSCOM_EXIT_GAME:
289       machine.Halt();
290       break;
291     case SYSCOM_SHOW_BACKGROUND:
292       graphics().ToggleInterfaceHidden();
293       break;
294     case SYSCOM_HIDE_MENU:
295       // Do nothing. The menu will be hidden on its own.
296       break;
297     case SYSCOM_GENERIC_1:
298     case SYSCOM_GENERIC_2:
299     case SYSCOM_SCREEN_MODE:
300     case SYSCOM_WINDOW_DECORATION_STYLE:
301       std::cerr << "No idea what to do!" << std::endl;
302       break;
303   }
304 }
305 
ShowSystemInfo(RLMachine & machine)306 void System::ShowSystemInfo(RLMachine& machine) {
307   if (platform_) {
308     RlvmInfo info;
309 
310     std::string regname = gameexe()("REGNAME").ToString("");
311     size_t pos = regname.find('\\');
312     if (pos != string::npos) {
313       info.game_brand = regname.substr(0, pos);
314       info.game_name = regname.substr(pos + 1);
315     } else {
316       info.game_brand = "";
317       info.game_name = regname;
318     }
319 
320     info.game_version = gameexe()("VERSION_STR").ToString("");
321     info.game_path = gameexe()("__GAMEPATH").ToString("");
322     info.rlvm_version = GetRlvmVersionString();
323     info.rlbabel_loaded = machine.DllLoaded("rlBabel");
324     info.text_transformation = machine.GetTextEncoding();
325 
326     platform_->ShowSystemInfo(machine, info);
327   }
328 }
329 
FindFile(const std::string & file_name,const std::vector<std::string> & extensions)330 boost::filesystem::path System::FindFile(
331     const std::string& file_name,
332     const std::vector<std::string>& extensions) {
333   if (filesystem_cache_.empty())
334     BuildFileSystemCache();
335 
336   // Hack to get around fileNames like "REALNAME?010", where we only
337   // want REALNAME.
338   std::string lower_name =
339       string(file_name.begin(), find(file_name.begin(), file_name.end(), '?'));
340   to_lower(lower_name);
341 
342   std::pair<FileSystemCache::const_iterator, FileSystemCache::const_iterator>
343       ret = filesystem_cache_.equal_range(lower_name);
344   for (const std::string& extension : extensions) {
345     for (FileSystemCache::const_iterator it = ret.first; it != ret.second;
346          ++it) {
347       if (extension == it->second.first) {
348         return it->second.second;
349       }
350     }
351   }
352 
353   // Error.
354   return fs::path();
355 }
356 
Reset()357 void System::Reset() {
358   in_menu_ = false;
359   previous_selection_.reset();
360 
361   EnableSyscom();
362 
363   sound().Reset();
364   graphics().Reset();
365   text().Reset();
366 }
367 
Regname()368 std::string System::Regname() {
369   Gameexe& gexe = gameexe();
370   std::string regname = gexe("REGNAME");
371   replace_all(regname, "\\", "_");
372 
373   // Note that we assume the Gameexe file is written in Shift-JIS. I don't
374   // think you can write it in anything else.
375   return cp932toUTF8(regname, 0);
376 }
377 
GameSaveDirectory()378 boost::filesystem::path System::GameSaveDirectory() {
379   fs::path base_dir = GetHomeDirectory() / ".rlvm" / Regname();
380   fs::create_directories(base_dir);
381 
382   return base_dir;
383 }
384 
ShouldFastForward()385 bool System::ShouldFastForward() {
386   return (event().CtrlPressed() && text().ctrl_key_skip()) ||
387          text().CurrentlySkipping() || force_fast_forward_;
388 }
389 
DumpRenderTree(RLMachine & machine)390 void System::DumpRenderTree(RLMachine& machine) {
391   std::ostringstream oss;
392   oss << "Dump_SEEN" << std::setw(4) << std::setfill('0')
393       << machine.SceneNumber() << "_Line" << machine.line_number() << ".txt";
394 
395   std::ofstream tree(oss.str().c_str());
396   graphics().Refresh(&tree);
397 }
398 
GetHomeDirectory()399 boost::filesystem::path System::GetHomeDirectory() {
400   std::string drive, home;
401   char* homeptr = getenv("HOME");
402   char* driveptr = getenv("HOMEDRIVE");
403   char* homepathptr = getenv("HOMEPATH");
404   char* profileptr = getenv("USERPROFILE");
405   if (homeptr != 0 && (home = homeptr) != "") {
406     // UN*X like home directory
407     return fs::path(home);
408   } else if (driveptr != 0 && homepathptr != 0 && (drive = driveptr) != "" &&
409              (home = homepathptr) != "") {
410     // Windows.
411     return fs::path(drive) / fs::path(home);
412   } else if (profileptr != 0 && (home = profileptr) != "") {
413     // Windows?
414     return fs::path(home);
415   } else {
416     throw SystemError("Could not find location of home directory.");
417   }
418 }
419 
InvokeSaveOrLoad(RLMachine & machine,int syscom,const std::string & mod_key,const std::string & location)420 void System::InvokeSaveOrLoad(RLMachine& machine,
421                               int syscom,
422                               const std::string& mod_key,
423                               const std::string& location) {
424   GameexeInterpretObject save_mod = gameexe()(mod_key);
425   GameexeInterpretObject save_loc = gameexe()(location);
426 
427   if (save_mod.Exists() && save_loc.Exists() && save_mod == 1) {
428     std::vector<int> raw_ints = save_loc;
429     int scenario = raw_ints.at(0);
430     int entrypoint = raw_ints.at(1);
431 
432     text().set_system_visible(false);
433     machine.PushLongOperation(new RestoreTextSystemVisibility);
434     machine.Farcall(scenario, entrypoint);
435   } else if (platform_) {
436     platform_->InvokeSyscomStandardUI(machine, syscom);
437   }
438 }
439 
CheckSyscomIndex(int index,const char * function)440 void System::CheckSyscomIndex(int index, const char* function) {
441   if (index < 0 || index >= NUM_SYSCOM_ENTRIES) {
442     std::ostringstream oss;
443     oss << "Illegal syscom index #" << index << " in " << function;
444     throw std::runtime_error(oss.str());
445   }
446 }
447 
BuildFileSystemCache()448 void System::BuildFileSystemCache() {
449   // First retrieve all the directories defined in the #FOLDNAME section.
450   std::vector<std::string> valid_directories;
451   Gameexe& gexe = gameexe();
452   GameexeFilteringIterator it = gexe.filtering_begin("FOLDNAME");
453   GameexeFilteringIterator end = gexe.filtering_end();
454   for (; it != end; ++it) {
455     std::string dir = it->ToString();
456     if (!dir.empty()) {
457       to_lower(dir);
458       valid_directories.push_back(dir);
459     }
460   }
461 
462   fs::path gamepath(gexe("__GAMEPATH").ToString());
463   fs::directory_iterator dir_end;
464   for (fs::directory_iterator dir(gamepath); dir != dir_end; ++dir) {
465     if (fs::is_directory(dir->status())) {
466       std::string lowername = dir->path().filename().string();
467       to_lower(lowername);
468       if (find(valid_directories.begin(), valid_directories.end(), lowername) !=
469           valid_directories.end()) {
470         AddDirectoryToCache(dir->path());
471       }
472     }
473   }
474 }
475 
AddDirectoryToCache(const fs::path & directory)476 void System::AddDirectoryToCache(const fs::path& directory) {
477   fs::directory_iterator dir_end;
478   for (fs::directory_iterator dir(directory); dir != dir_end; ++dir) {
479     if (fs::is_directory(dir->status())) {
480       AddDirectoryToCache(dir->path());
481     } else {
482       std::string extension = dir->path().extension().string();
483       if (extension.size() > 1 && extension[0] == '.')
484         extension = extension.substr(1);
485       to_lower(extension);
486 
487       if (find(ALL_FILETYPES.begin(), ALL_FILETYPES.end(), extension) !=
488           ALL_FILETYPES.end()) {
489         std::string stem = dir->path().stem().string();
490         to_lower(stem);
491 
492         filesystem_cache_.emplace(stem, make_pair(extension, dir->path()));
493       }
494     }
495   }
496 }
497 
GetRlvmVersionString()498 std::string GetRlvmVersionString() { return "Version 0.14"; }
499