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