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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26
// -----------------------------------------------------------------------
28
#include <boost/program_options.hpp>
29
#include <boost/filesystem/operations.hpp>
31
// We include this here because SDL is retarded and works by #define
32
// main(inat argc, char* agrv[]). Loosers.
39
#include "MachineBase/GameHacks.hpp"
40
#include "MachineBase/RLMachine.hpp"
41
#include "MachineBase/Serialization.hpp"
42
#include "Modules/Modules.hpp"
43
#include "Modules/Module_Sys_Save.hpp"
44
#include "Platforms/gcn/GCNPlatform.hpp"
45
#include "Systems/Base/GraphicsSystem.hpp"
46
#include "Systems/Base/SoundSystem.hpp"
47
#include "Systems/Base/SystemError.hpp"
48
#include "Systems/SDL/SDLSystem.hpp"
49
#include "Utilities/Exception.hpp"
50
#include "Utilities/File.hpp"
51
#include "Utilities/findFontFile.h"
52
#include "libReallive/gameexe.h"
53
#include "libReallive/reallive.h"
57
namespace po = boost::program_options;
58
namespace fs = boost::filesystem;
61
* @mainpage RLVM, a Reallive virtual machine clone
63
* @section Introduction
65
* RLVM is a clone of the official Reallive virtual machine, produced
66
* by VisualArt's KK, meant to provide Linux and Macintosh users with
67
* a compatible, portable interpreter for Realive games.
69
* RLVM would not exist if it weren't for the help of Haeleth, and,
70
* indirectly, Jagarl, both of who have done amazing jobs documenting
71
* the fine details of the RealLive system, along with doing most of
72
* the really hard reverse engineering work that I'd rather not do.
74
* @section Table Table of Contents
76
* The documentation is divided into the following sections.
78
* - @subpage theProblemDomainOfVisualNovels "The problem Domain of Visual Novels"
79
* - @subpage architectureReview "RLVM Architecture"
82
// -----------------------------------------------------------------------
85
* @page theProblemDomainOfVisualNovels The problem Domain of Visual Novels
89
* Visual Novels (referred to as NVL, AVG or ADV games in Japanese) are
90
* a form of interactive fiction in Japan which never really became
91
* popular in most English speaking countries. They are simple, plot
92
* and character oriented games which are very text-heavy. Gameplay
93
* wise, they are comparable to a large slide show with text, images
94
* and sound, and can be thought of as massive, more serious, mature
95
* versions of the Choose-Your-Own-Adventure series of children's books.
97
* @section IsAndIsnt What RLVM is and isn't
99
* RLVM is a clone of a specific visual novel interpreter, the
100
* RealLive system developed by VisualArts KK. It aims to (eventually)
101
* become a compatible, portable interpreter for non-Windows users
102
* that will play a large variety of commercial visual novels written
105
* RLVM is not intended to compete with VisualArts KK as a development
106
* toolkit. While someone could theoretically combine RLVM with <a
107
* href="http://www.haeleth.net">Haeleth</a>'s <a
108
* href="http://dev.haeleth.net/rldev.shtml">RLdev</a> compiler
109
* toolkit to produce games (at least after RLVM supports a base set
110
* of operations), it would be overly cumbersome and I would recommend
111
* one of the many free visual novel development systems, which would
112
* be both easier to use and more featurefull.
114
* RLVM is not meant to facilitate piracy. Please buy these games;
115
* many people put their hearts into writing these stories and they
116
* deserve to be rewarded financially.
118
* Finally, RLVM is not a big truck...It's a series of tubes.
121
// -----------------------------------------------------------------------
124
* @page architectureReview RLVM Architecture
126
* RLVM is divided into several high level directories:
128
* - A modified version of Haeleth's @c libReallive, which is
129
* responsible for reading and parsing the SEEN.TXT file and
130
* creating the corresponding object representation. There is also a
131
* class Gameexe which parses the Gameexe.ini file in every RealLive
133
* - The Opcode Definitions / Modules, which can be found in the
134
* subidrectory @c src/Modules/ . These files contain the
135
* definitions for the individual Opcodes.
136
* - The core of the virtual machine found in @c src/MachineBase/ :
137
* - RLMachine: the main class which contains all execution state
138
* - RLOperation: the base class of every opcode definition.
139
* - LongOperation: the base class for all operations that persist
140
* for multiple cycles through the game loop.
141
* - The Base System classes found in @c src/Systems/Base , which define the
142
* generalized interface for system dependent operations like sound and
143
* graphics. It was originally meant to only define abstract interface which
144
* would be implemented in System/ specific directories. Said base classes
145
* have grown to hold significant cross-platform logic, implemented per
146
* system by subclassing. It has also grown to contain a whole lot of
147
* non-subclassed, cross platform code.
148
* - The System subclasses, such as @c src/Systems/SDL , which
149
* implement the Base System interface for SDL. Additional
150
* subclasses could be written for DirectX, or some other game
153
* The above was part of the original version of this document. In the
154
* intervening time, I've also added:
156
* - LongOperations and Effects: LongOperations are functors that get run
157
* during the mainloop instead of a RealLive instruction. Sometimes execution
158
* of RealLive code needs to stop so a command that would run longer than a
159
* single run through the gameloop can take all the time it needs while still
160
* updating the screen. Effects are graphical LongOperations that transition
161
* between two images.
162
* - Encodings: A combination of Haeleth's encoding functions from rlBabel and
163
* various encoding utility functions I've written.
166
// -----------------------------------------------------------------------
168
void printVersionInformation() {
170
<< "rlvm (" << rlvm_version() << ")" << endl
171
<< "Copyright (C) 2006-2011 Elliot Glaysher, et all." << endl
173
<< "Contains code that is: " << endl
174
<< " Copyright (C) 2006-2007 Peter \"Haeleth\" Jolly" << endl
175
<< " Copyright (C) 2004-2006 Kazunori \"jagarl\" Ueno" << endl
177
<< "This program is free software: you can redistribute it and/or modify"
179
<< "it under the terms of the GNU General Public License as published by"
181
<< "the Free Software Foundation, either version 3 of the License, or"
183
<< "(at your option) any later version."
185
<< "This program is distributed in the hope that it will be useful,"
187
<< "but WITHOUT ANY WARRANTY; without even the implied warranty of"
189
<< "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the"
191
<< "GNU General Public License for more details."
193
<< "You should have received a copy of the GNU General Public License"
195
<< "along with this program. If not, see <http://www.gnu.org/licenses/>."
199
// -----------------------------------------------------------------------
201
void printUsage(const string& name, po::options_description& opts) {
202
cout << "Usage: " << name << " [options] <game root>" << endl;
203
cout << opts << endl;
206
// -----------------------------------------------------------------------
208
int main(int argc, char* argv[]) {
211
// Set global state: allow spaces in game paths
212
fs::path::default_name_check(fs::native);
214
// -----------------------------------------------------------------------
215
// Parse command line options
217
// Declare the supported options.
218
po::options_description opts("Options");
220
("help", "Produce help message")
221
("help-debug", "Print help message for people working on rlvm")
222
("version", "Display version and license information")
223
("font", po::value<string>(), "Specifies TrueType font to use.");
225
po::options_description debugOpts("Debugging Options");
226
debugOpts.add_options()
227
("gameexe", po::value<string>(), "Override location of Gameexe.ini")
228
("seen", po::value<string>(), "Override location of SEEN.TXT")
229
("start-seen", po::value<int>(), "Force start at SEEN#")
230
("load-save", po::value<int>(), "Load a saved game on start")
231
("memory", "Forces debug mode (Sets #MEMORY=1 in the Gameexe.ini file)")
232
("undefined-opcodes", "Display a message on undefined opcodes")
234
"On exit, present a summary table about how many times each undefined "
235
"opcode was called");
237
// Declare the final option to be game-root
238
po::options_description hidden("Hidden");
240
("game-root", po::value<string>(), "Location of game root");
242
po::positional_options_description p;
243
p.add("game-root", -1);
245
// Use these on the command line
246
po::options_description commandLineOpts;
247
commandLineOpts.add(opts).add(hidden).add(debugOpts);
249
po::variables_map vm;
251
po::store(po::basic_command_line_parser<char>(argc, argv).
252
options(commandLineOpts).positional(p).run(),
255
} catch (boost::program_options::multiple_occurrences& e) {
256
cerr << "Couldn't parse command line: multiple_occurances." << endl
257
<< " (Hint: this can happen when your shell doesn't escape properly,"
259
<< " e.g. \"/path/to/Clannad Full Voice/\" without the quotes.)"
262
} catch (boost::program_options::error& e) {
263
cerr << "Couldn't parse command line: " << e.what() << endl;
267
// -----------------------------------------------------------------------
269
po::options_description allOpts("Allowed options");
270
allOpts.add(opts).add(debugOpts);
272
// -----------------------------------------------------------------------
273
// Process command line options
274
fs::path gamerootPath, gameexePath, seenPath;
276
if (vm.count("help")) {
277
printUsage(argv[0], opts);
281
if (vm.count("help-debug")) {
282
printUsage(argv[0], allOpts);
286
if (vm.count("version")) {
287
printVersionInformation();
291
if (vm.count("game-root")) {
292
gamerootPath = vm["game-root"].as<string>();
294
if (!fs::exists(gamerootPath)) {
295
cerr << "ERROR: Path '" << gamerootPath << "' does not exist." << endl;
299
if (!fs::is_directory(gamerootPath)) {
300
cerr << "ERROR: Path '" << gamerootPath << "' is not a directory."
305
// Some games hide data in a lower subdirectory. A little hack to
306
// make these behave as expected...
307
if (correctPathCase(gamerootPath / "Gameexe.ini").empty()) {
308
if (!correctPathCase(gamerootPath / "KINETICDATA" /
309
"Gameexe.ini").empty()) {
310
gamerootPath /= "KINETICDATA/";
311
} else if (!correctPathCase(gamerootPath / "REALLIVEDATA" /
312
"Gameexe.ini").empty()) {
313
gamerootPath /= "REALLIVEDATA/";
315
cerr << "WARNING: Path '" << gamerootPath << "' may not contain a "
316
<< "RealLive game." << endl;
320
printUsage(argv[0], opts);
325
if (vm.count("gameexe"))
326
gameexePath = correctPathCase(vm["gameexe"].as<string>());
328
gameexePath = correctPathCase(gamerootPath / "Gameexe.ini");
331
if (vm.count("seen"))
332
seenPath = correctPathCase(vm["seen"].as<string>());
334
seenPath = correctPathCase(gamerootPath / "Seen.txt");
337
cerr << "gameexePath: " << gameexePath << endl;
338
Gameexe gameexe(gameexePath);
339
gameexe("__GAMEPATH") = gamerootPath.file_string();
341
if (vm.count("font")) {
342
string font = vm["font"].as<string>();
343
if (fs::exists(font)) {
344
gameexe("__GAMEFONT") = font;
345
cerr << "Using custom font " << vm["font"].as<string>() << endl;
347
cerr << "Couldn't open font file \"" << font << "\"" << endl;
352
// Possibly force starting at a different seen
353
if (vm.count("start-seen"))
354
gameexe("SEEN_START") = vm["start-seen"].as<int>();
356
if (vm.count("memory"))
357
gameexe("MEMORY") = 1;
359
SDLSystem sdlSystem(gameexe);
360
libReallive::Archive arc(seenPath.file_string(), gameexe("REGNAME"));
361
RLMachine rlmachine(sdlSystem, arc);
362
addAllModules(rlmachine);
363
addGameHacks(rlmachine);
365
// Validate our font file
366
fs::path fontFile = findFontFile(sdlSystem);
367
if (fontFile.empty() || !fs::exists(fontFile)) {
368
cerr << "Could not open font file. Please either: " << endl
370
<< "1) Place a copy of msgothic.ttc in your home directory." << endl
371
<< "2) Place a copy of msgothic.ttc in \""
372
<< gamerootPath.file_string() << "\"" << endl
373
<< "3) Specify an alternate font with the --font option." << endl;
377
// Initialize our platform dialogs (we have to do this after
378
// looking for a font because we use that font internally).
379
boost::shared_ptr<Platform> platform(
380
new GCNPlatform(sdlSystem, sdlSystem.graphics().screenRect()));
381
sdlSystem.setPlatform(platform);
383
if (vm.count("undefined-opcodes"))
384
rlmachine.setPrintUndefinedOpcodes(true);
386
if (vm.count("count-undefined"))
387
rlmachine.recordUndefinedOpcodeCounts();
389
Serialization::loadGlobalMemory(rlmachine);
390
rlmachine.setHaltOnException(false);
392
if (vm.count("load-save")) {
393
Sys_load()(rlmachine, vm["load-save"].as<int>());
396
while (!rlmachine.halted()) {
397
// Give SDL a chance to respond to events, redraw the screen,
399
sdlSystem.run(rlmachine);
401
// Run the rlmachine through another instruction
402
rlmachine.executeNextInstruction();
405
Serialization::saveGlobalMemory(rlmachine);
406
} catch (rlvm::Exception& e) {
407
cerr << "Fatal RLVM error: " << e.what() << endl;
409
} catch (libReallive::Error& e) {
410
cerr << "Fatal libReallive error: " << e.what() << endl;
412
} catch (SystemError& e) {
413
cerr << "Fatal local system error: " << e.what() << endl;
415
} catch (std::exception& e) {
416
cout << "Uncaught exception: " << e.what() << endl;
418
} catch (const char* e) {
419
cout << "Uncaught exception: " << e << endl;