1
// -*- Mode: C++; tab-width:2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
// vi:tw=80:et:ts=2:sts=2
4
// -----------------------------------------------------------------------
6
// This file is part of RLVM, a RealLive virtual machine clone.
8
// -----------------------------------------------------------------------
10
// Copyright (C) 2006, 2007 Elliot Glaysher
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.
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.
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.
26
// -----------------------------------------------------------------------
28
#include "Systems/Base/System.hpp"
31
#include <boost/algorithm/string.hpp>
32
#include <boost/assign/list_of.hpp> // for 'list_of()'
33
#include <boost/bind.hpp>
34
#include <boost/filesystem/convenience.hpp>
35
#include <boost/filesystem/operations.hpp>
36
#include <boost/filesystem/path.hpp>
43
#include "LongOperations/LoadGameLongOperation.hpp"
44
#include "MachineBase/LongOperation.hpp"
45
#include "MachineBase/RLMachine.hpp"
46
#include "MachineBase/Serialization.hpp"
47
#include "Modules/Module_Sys.hpp"
48
#include "Systems/Base/EventSystem.hpp"
49
#include "Systems/Base/GraphicsSystem.hpp"
50
#include "Systems/Base/Platform.hpp"
51
#include "Systems/Base/RlvmInfo.hpp"
52
#include "Systems/Base/SoundSystem.hpp"
53
#include "Systems/Base/SystemError.hpp"
54
#include "Systems/Base/TextSystem.hpp"
55
#include "Utilities/Exception.hpp"
56
#include "Utilities/StringUtilities.hpp"
57
#include "libReallive/gameexe.h"
60
using boost::assign::list_of;
62
using boost::replace_all;
63
using boost::to_lower;
65
namespace fs = boost::filesystem;
69
const std::vector<std::string> ALL_FILETYPES =
70
list_of("g00")("pdt")("anm")("gan")("hik")("wav")("ogg")("nwa")("mp3")
71
("ovk")("koe")("nwk");
73
struct LoadingGameFromStream : public LoadGameLongOperation {
74
LoadingGameFromStream(RLMachine& machine,
75
const boost::shared_ptr<std::stringstream>& selection)
76
: LoadGameLongOperation(machine),
77
selection_(selection) {}
79
virtual void load(RLMachine& machine) {
80
// We need to copy data here onto the stack because the action of loading
81
// will deallocate this object.
82
boost::shared_ptr<std::stringstream> s = selection_;
83
Serialization::loadGameFrom(*s, machine);
84
// Warning: |this| is an invalid pointer now.
87
boost::shared_ptr<std::stringstream> selection_;
92
// I assume GAN files can't go through the OBJ_FILETYPES path.
93
const std::vector<std::string> OBJ_FILETYPES =
94
list_of("anm")("g00")("pdt");
95
const std::vector<std::string> IMAGE_FILETYPES =
96
list_of("g00")("pdt");
97
const std::vector<std::string> PDT_IMAGE_FILETYPES =
99
const std::vector<std::string> GAN_FILETYPES =
101
const std::vector<std::string> ANM_FILETYPES =
103
const std::vector<std::string> HIK_FILETYPES =
104
list_of("hik")("g00")("pdt");
105
const std::vector<std::string> SOUND_FILETYPES =
106
list_of("wav")("ogg")("nwa")("mp3");
107
const std::vector<std::string> KOE_ARCHIVE_FILETYPES =
108
list_of("ovk")("koe")("nwk");
109
const std::vector<std::string> KOE_LOOSE_FILETYPES =
112
class MenuReseter : public LongOperation {
114
explicit MenuReseter(System& sys) : sys_(sys) {}
116
bool operator()(RLMachine& machine) {
117
sys_.in_menu_ = false;
125
// -----------------------------------------------------------------------
127
// -----------------------------------------------------------------------
129
SystemGlobals::SystemGlobals()
130
: confirm_save_load_(true), low_priority_(false) {}
133
// -----------------------------------------------------------------------
135
// -----------------------------------------------------------------------
139
force_fast_forward_(false),
141
use_western_font_(false) {
142
fill(syscom_status_, syscom_status_ + NUM_SYSCOM_ENTRIES, SYSCOM_VISIBLE);
148
void System::takeSelectionSnapshot(RLMachine& machine) {
149
previous_selection_.reset(new std::stringstream);
150
Serialization::saveGameTo(*previous_selection_, machine);
153
void System::restoreSelectionSnapshot(RLMachine& machine) {
154
// We need to reference this on the stack because it will call
155
// System::reset() to get the black screen. (We'll reset again inside
156
// LoadingGameFromStream.)
157
boost::shared_ptr<std::stringstream> s = previous_selection_;
159
// LoadingGameFromStream adds itself to the callstack of |machine| due to
160
// subtle timing issues.
161
new LoadingGameFromStream(machine, s);
165
int System::isSyscomEnabled(int syscom) {
166
checkSyscomIndex(syscom, "System::is_syscom_enabled");
168
// Special cases where state of the interpreter would override the
169
// programmatically set (or user set) values.
170
if (syscom == SYSCOM_SET_SKIP_MODE && !text().kidokuRead()) {
171
// Skip mode should be grayed out when there's no text to read
172
if (syscom_status_[syscom] == SYSCOM_VISIBLE)
173
return SYSCOM_GREYED_OUT;
174
} else if (syscom == SYSCOM_RETURN_TO_PREVIOUS_SELECTION) {
175
if (syscom_status_[syscom] == SYSCOM_VISIBLE)
176
return previous_selection_.get() ? SYSCOM_VISIBLE : SYSCOM_GREYED_OUT;
179
return syscom_status_[syscom];
182
void System::hideSyscom() {
183
fill(syscom_status_, syscom_status_ + NUM_SYSCOM_ENTRIES, SYSCOM_INVISIBLE);
186
void System::hideSyscomEntry(int syscom) {
187
checkSyscomIndex(syscom, "System::hide_system");
188
syscom_status_[syscom] = SYSCOM_INVISIBLE;
191
void System::enableSyscom() {
192
fill(syscom_status_, syscom_status_ + NUM_SYSCOM_ENTRIES, SYSCOM_VISIBLE);
195
void System::enableSyscomEntry(int syscom) {
196
checkSyscomIndex(syscom, "System::enable_system");
197
syscom_status_[syscom] = SYSCOM_VISIBLE;
200
void System::disableSyscom() {
201
fill(syscom_status_, syscom_status_ + NUM_SYSCOM_ENTRIES, SYSCOM_GREYED_OUT);
204
void System::disableSyscomEntry(int syscom) {
205
checkSyscomIndex(syscom, "System::disable_system");
206
syscom_status_[syscom] = SYSCOM_GREYED_OUT;
209
int System::readSyscom(int syscom) {
210
throw rlvm::Exception("ReadSyscom unimplemented!");
213
void System::showSyscomMenu(RLMachine& machine) {
214
Gameexe& gexe = machine.system().gameexe();
216
if (gexe("CANCELCALL_MOD") == 1) {
218
// Multiple right clicks shouldn't spawn multiple copies of the menu
219
// system on top of each other.
221
machine.pushLongOperation(new MenuReseter(*this));
223
vector<int> cancelcall = gexe("CANCELCALL");
224
machine.farcall(cancelcall.at(0), cancelcall.at(1));
226
} else if (platform_) {
227
platform_->showNativeSyscomMenu(machine);
229
cerr << "(We don't deal with non-custom SYSCOM calls yet.)" << endl;
233
void System::invokeSyscom(RLMachine& machine, int syscom) {
236
invokeSaveOrLoad(machine, syscom, "SYSTEMCALL_SAVE_MOD", "SYSTEMCALL_SAVE");
239
invokeSaveOrLoad(machine, syscom, "SYSTEMCALL_LOAD_MOD", "SYSTEMCALL_LOAD");
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:
251
case SYSCOM_DISPLAY_VERSION: {
253
platform_->invokeSyscomStandardUI(machine, syscom);
256
case SYSCOM_RETURN_TO_PREVIOUS_SELECTION:
257
restoreSelectionSnapshot(machine);
259
case SYSCOM_SHOW_WEATHER:
260
graphics().setShowWeather(!graphics().showWeather());
262
case SYSCOM_SHOW_OBJECT_1:
263
graphics().setShowObject1(!graphics().showObject1());
265
case SYSCOM_SHOW_OBJECT_2:
266
graphics().setShowObject2(!graphics().showObject2());
268
case SYSCOM_CLASSIFY_TEXT:
269
cerr << "We have no idea what classifying text even means!" << endl;
271
case SYSCOM_OPEN_MANUAL_PATH:
272
cerr << "Opening manual path..." << endl;
274
case SYSCOM_SET_SKIP_MODE:
275
text().setSkipMode(!text().skipMode());
277
case SYSCOM_AUTO_MODE:
278
text().setAutoMode(!text().autoMode());
280
case SYSCOM_MENU_RETURN:
281
// This is a hack since we probably have a bunch of crap on the stack.
282
machine.clearLongOperationsOffBackOfStack();
284
// Simulate a MenuReturn.
285
Sys_MenuReturn()(machine);
287
case SYSCOM_EXIT_GAME:
290
case SYSCOM_SHOW_BACKGROUND:
291
graphics().toggleInterfaceHidden();
293
case SYSCOM_HIDE_MENU:
294
// Do nothing. The menu will be hidden on its own.
296
case SYSCOM_GENERIC_1:
297
case SYSCOM_GENERIC_2:
298
case SYSCOM_SCREEN_MODE:
299
case SYSCOM_WINDOW_DECORATION_STYLE:
300
cerr << "No idea what to do!" << endl;
305
void System::showSystemInfo(RLMachine& machine) {
309
string regname = gameexe()("REGNAME").to_string("");
310
size_t pos = regname.find('\\');
311
if (pos != string::npos) {
312
info.game_brand = regname.substr(0, pos);
313
info.game_name = regname.substr(pos + 1);
315
info.game_brand = "";
316
info.game_name = regname;
319
info.game_version = gameexe()("VERSION_STR").to_string("");
320
info.game_path = gameexe()("__GAMEPATH").to_string("");
321
info.rlvm_version = rlvm_version();
322
info.rlbabel_loaded = machine.dllLoaded("rlBabel");
323
info.text_transformation = machine.getTextEncoding();
325
platform_->showSystemInfo(machine, info);
329
boost::filesystem::path System::findFile(
330
const std::string& file_name,
331
const std::vector<std::string>& extensions) {
332
if (filesystem_cache_.empty())
333
buildFileSystemCache();
335
// Hack to get around fileNames like "REALNAME?010", where we only
338
string(file_name.begin(), find(file_name.begin(), file_name.end(), '?'));
339
to_lower(lower_name);
341
std::pair<FileSystemCache::const_iterator, FileSystemCache::const_iterator>
342
ret = filesystem_cache_.equal_range(lower_name);
343
for (vector<string>::const_iterator ext = extensions.begin();
344
ext != extensions.end(); ++ext) {
345
for (FileSystemCache::const_iterator it = ret.first;
346
it != ret.second; ++it) {
347
if (*ext == it->second.first) {
348
return it->second.second;
357
void System::reset() {
359
previous_selection_.reset();
368
std::string System::regname() {
369
Gameexe& gexe = gameexe();
370
string regname = gexe("REGNAME");
371
replace_all(regname, "\\", "_");
373
// Note that we assume the Gameexe file is written in Shift-JIS. I don't
374
// think you can write it in anyhting else.
375
return cp932toUTF8(regname, 0);
378
boost::filesystem::path System::gameSaveDirectory() {
379
fs::path base_dir = getHomeDirectory() / ".rlvm" / regname();
380
fs::create_directories(base_dir);
385
bool System::fastForward() {
386
return (event().ctrlPressed() && text().ctrlKeySkip()) ||
387
text().currentlySkipping() ||
391
void System::dumpRenderTree(RLMachine& machine) {
393
oss << "Dump_SEEN" << setw(4) << setfill('0') << machine.sceneNumber()
394
<< "_Line" << machine.lineNumber() << ".txt";
396
ofstream tree(oss.str().c_str());
397
graphics().refresh(&tree);
400
boost::filesystem::path System::getHomeDirectory() {
402
char *homeptr = getenv("HOME");
403
char *driveptr = getenv("HOMEDRIVE");
404
char *homepathptr = getenv("HOMEPATH");
405
char *profileptr = getenv("USERPROFILE");
406
if (homeptr != 0 && (home = homeptr) != "") {
407
// UN*X like home directory
408
return fs::path(home);
409
} else if (driveptr != 0 &&
411
(drive = driveptr) != "" &&
412
(home = homepathptr) != "") {
414
return fs::path(drive) / fs::path(home);
415
} else if (profileptr != 0 && (home = profileptr) != "") {
417
return fs::path(home);
419
throw SystemError("Could not find location of home directory.");
423
void System::invokeSaveOrLoad(RLMachine& machine,
425
const std::string& mod_key,
426
const std::string& location) {
427
GameexeInterpretObject save_mod = gameexe()(mod_key);
428
GameexeInterpretObject save_loc = gameexe()(location);
430
if (save_mod.exists() && save_loc.exists() && save_mod == 1) {
431
vector<int> raw_ints = save_loc;
432
int scenario = raw_ints.at(0);
433
int entrypoint = raw_ints.at(1);
435
text().setSystemVisible(false);
436
machine.pushLongOperation(new RestoreTextSystemVisibility);
437
machine.farcall(scenario, entrypoint);
438
} else if (platform_) {
439
platform_->invokeSyscomStandardUI(machine, syscom);
443
void System::checkSyscomIndex(int index, const char* function) {
444
if (index < 0 || index >= NUM_SYSCOM_ENTRIES) {
446
oss << "Illegal syscom index #" << index << " in " << function;
447
throw std::runtime_error(oss.str());
451
void System::buildFileSystemCache() {
452
// First retrieve all the directories defined in the #FOLDNAME section.
453
std::vector<std::string> valid_directories;
454
Gameexe& gexe = gameexe();
455
GameexeFilteringIterator it = gexe.filtering_begin("FOLDNAME");
456
GameexeFilteringIterator end = gexe.filtering_end();
457
for (; it != end; ++it) {
458
std::string dir = it->to_string();
461
valid_directories.push_back(dir);
465
fs::path gamepath(gexe("__GAMEPATH").to_string());
466
fs::directory_iterator dir_end;
467
for (fs::directory_iterator dir(gamepath); dir != dir_end; ++dir) {
468
if (fs::is_directory(dir->status())) {
469
std::string lowername = dir->path().filename();
471
if (find(valid_directories.begin(), valid_directories.end(), lowername) !=
472
valid_directories.end()) {
473
addDirectoryToCache(dir->path());
479
void System::addDirectoryToCache(const fs::path& directory) {
480
fs::directory_iterator dir_end;
481
for (fs::directory_iterator dir(directory); dir != dir_end; ++dir) {
482
if (fs::is_directory(dir->status())) {
483
addDirectoryToCache(dir->path());
485
std::string extension = dir->path().extension();
486
if (extension.size() > 1 && extension[0] == '.')
487
extension = extension.substr(1);
490
if (find(ALL_FILETYPES.begin(), ALL_FILETYPES.end(), extension) !=
491
ALL_FILETYPES.end()) {
492
std::string stem = dir->path().stem();
495
filesystem_cache_.insert(
497
make_pair(extension, dir->path())));
503
std::string rlvm_version() {
504
return "Version 0.12";