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) 2011 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 #include "machine/rlvm_instance.h"
28
29 #include <iostream>
30 #include <string>
31
32 #include "libreallive/gameexe.h"
33 #include "libreallive/reallive.h"
34 #include "machine/dump_scenario.h"
35 #include "machine/game_hacks.h"
36 #include "machine/memory.h"
37 #include "machine/rlmachine.h"
38 #include "machine/serialization.h"
39 #include "modules/module_sys_save.h"
40 #include "modules/modules.h"
41 #include "platforms/gcn/gcn_platform.h"
42 #include "systems/base/event_system.h"
43 #include "systems/base/graphics_system.h"
44 #include "systems/base/system_error.h"
45 #include "systems/sdl/sdl_system.h"
46 #include "utf8cpp/utf8.h"
47 #include "utilities/exception.h"
48 #include "utilities/file.h"
49 #include "utilities/find_font_file.h"
50 #include "utilities/gettext.h"
51 #include "utilities/string_utilities.h"
52
53 namespace fs = boost::filesystem;
54
55 // AVG32 file checks. We can't run AVG32 games.
56 const char* avg32_exes[] = {"avg3216m.exe", "avg3217m.exe", NULL};
57
58 // Siglus engine filenames. We can't run VisualArts' newer engine.
59 const char* siglus_exes[] = {"siglus.exe", "siglusengine-ch.exe",
60 "siglusengine.exe", "siglusenginechs.exe",
61 NULL};
62
RLVMInstance()63 RLVMInstance::RLVMInstance()
64 : seen_start_(-1),
65 memory_(false),
66 undefined_opcodes_(false),
67 count_undefined_copcodes_(false),
68 tracing_(false),
69 load_save_(-1),
70 dump_seen_(-1) {
71 srand(time(NULL));
72 }
73
~RLVMInstance()74 RLVMInstance::~RLVMInstance() {}
75
Run(const boost::filesystem::path & gamerootPath)76 void RLVMInstance::Run(const boost::filesystem::path& gamerootPath) {
77 try {
78 fs::path gameexePath = FindGameFile(gamerootPath, "Gameexe.ini");
79 fs::path seenPath = FindGameFile(gamerootPath, "Seen.txt");
80
81 // Check for VisualArt's older and newer engines, which we can't emulate:
82 CheckBadEngine(gamerootPath, avg32_exes, _("Can't run AVG32 games"));
83 CheckBadEngine(gamerootPath, siglus_exes, _("Can't run Siglus games"));
84
85 Gameexe gameexe(gameexePath);
86 gameexe("__GAMEPATH") = gamerootPath.string();
87
88 // Possibly force starting at a different seen
89 if (seen_start_ != -1)
90 gameexe("SEEN_START") = seen_start_;
91
92 if (memory_)
93 gameexe("MEMORY") = 1;
94
95 if (!custom_font_.empty()) {
96 if (!fs::exists(custom_font_)) {
97 throw rlvm::UserPresentableError(
98 _("Could not open font file."),
99 _("Please make sure the font file specified with --font exists and "
100 "is a TrueType font."));
101 }
102
103 gameexe("__GAMEFONT") = custom_font_;
104 }
105
106 libreallive::Archive arc(seenPath.string(), gameexe("REGNAME"));
107 SDLSystem sdlSystem(gameexe);
108 RLMachine rlmachine(sdlSystem, arc);
109 AddAllModules(rlmachine);
110 AddGameHacks(rlmachine);
111
112 if (dump_seen_ != -1) {
113 libreallive::Scenario* scenario = arc.GetScenario(dump_seen_);
114 DumpScenario(&rlmachine, scenario);
115 return;
116 }
117
118 // Validate our font file
119 // TODO(erg): Remove this when we switch to native font selection dialogs.
120 fs::path fontFile = FindFontFile(sdlSystem);
121 if (fontFile.empty() || !fs::exists(fontFile)) {
122 throw rlvm::UserPresentableError(
123 _("Could not find msgothic.ttc or a suitable fallback font."),
124 _("Please place a copy of msgothic.ttc in either your home directory "
125 "or in the game path."));
126 }
127
128 // Initialize our platform dialogs (we have to do this after
129 // looking for a font because we use that font internally).
130 std::shared_ptr<GCNPlatform> platform(
131 new GCNPlatform(sdlSystem, sdlSystem.graphics().screen_rect()));
132 sdlSystem.SetPlatform(platform);
133
134 if (undefined_opcodes_)
135 rlmachine.SetPrintUndefinedOpcodes(true);
136
137 if (count_undefined_copcodes_)
138 rlmachine.RecordUndefinedOpcodeCounts();
139
140 if (tracing_)
141 rlmachine.set_tracing_on();
142
143 Serialization::loadGlobalMemory(rlmachine);
144
145 // Now to preform a quick integrity check. If the user opened the Japanese
146 // version of CLANNAD (or any other game), and then installed a patch, our
147 // user data is going to be screwed!
148 DoUserNameCheck(rlmachine);
149
150 rlmachine.SetHaltOnException(false);
151
152 if (load_save_ != -1)
153 Sys_load()(rlmachine, load_save_);
154
155 while (!rlmachine.halted()) {
156 // Give SDL a chance to respond to events, redraw the screen,
157 // etc.
158 sdlSystem.Run(rlmachine);
159
160 // Run the rlmachine through as many instructions as we can in a 10ms time
161 // slice. Bail out if we switch to long operation mode, or if the screen
162 // is marked as dirty.
163 unsigned int start_ticks = sdlSystem.event().GetTicks();
164 unsigned int end_ticks = start_ticks;
165 do {
166 rlmachine.ExecuteNextInstruction();
167 end_ticks = sdlSystem.event().GetTicks();
168 } while (!rlmachine.CurrentLongOperation() &&
169 !sdlSystem.force_wait() &&
170 (end_ticks - start_ticks < 10));
171
172 // Sleep to be nice to the processor and to give the GPU a chance to
173 // catch up.
174 if (!sdlSystem.ShouldFastForward()) {
175 int real_sleep_time = 10 - (end_ticks - start_ticks);
176 if (real_sleep_time < 1)
177 real_sleep_time = 1;
178 sdlSystem.event().Wait(real_sleep_time);
179 }
180
181 sdlSystem.set_force_wait(false);
182 }
183
184 Serialization::saveGlobalMemory(rlmachine);
185 }
186 catch (rlvm::UserPresentableError& e) {
187 ReportFatalError(e.message_text(), e.informative_text());
188 }
189 catch (rlvm::Exception& e) {
190 ReportFatalError(_("Fatal RLVM error"), e.what());
191 }
192 catch (libreallive::Error& e) {
193 ReportFatalError(_("Fatal libreallive error"), e.what());
194 }
195 catch (SystemError& e) {
196 ReportFatalError(_("Fatal local system error"), e.what());
197 }
198 catch (std::exception& e) {
199 ReportFatalError(_("Uncaught exception"), e.what());
200 }
201 catch (const char* e) {
202 ReportFatalError(_("Uncaught exception"), e);
203 }
204 }
205
SelectGameDirectory()206 boost::filesystem::path RLVMInstance::SelectGameDirectory() {
207 return boost::filesystem::path();
208 }
209
ReportFatalError(const std::string & message_text,const std::string & informative_text)210 void RLVMInstance::ReportFatalError(const std::string& message_text,
211 const std::string& informative_text) {
212 std::cerr << message_text << ": " << informative_text << std::endl;
213 }
214
DoUserNameCheck(RLMachine & machine)215 void RLVMInstance::DoUserNameCheck(RLMachine& machine) {
216 try {
217 int encoding = machine.GetProbableEncodingType();
218
219 // Iterate over all the names in both global and local memory banks.
220 GlobalMemory& g = machine.memory().global();
221 for (int i = 0; i < SIZE_OF_NAME_BANK; ++i)
222 cp932toUTF8(g.global_names[i], encoding);
223
224 LocalMemory& l = machine.memory().local();
225 for (int i = 0; i < SIZE_OF_NAME_BANK; ++i)
226 cp932toUTF8(l.local_names[i], encoding);
227 }
228 catch (...) {
229 // We've failed to interpret one of the name strings as a string in the
230 // text encoding of the current native encoding. We're going to fail to
231 // display any line that refers to the player's name.
232 //
233 // That's obviously bad and there's no real way to recover from this so
234 // just reset all of global memory.
235 if (AskUserPrompt(
236 _("Corrupted global memory"),
237 _("You appear to have run this game without a translation patch "
238 "previously. This can cause lines of text to not print."),
239 _("Reset"),
240 _("Continue with broken names"))) {
241 machine.HardResetMemory();
242 }
243 }
244 }
245
FindGameFile(const boost::filesystem::path & gamerootPath,const std::string & filename)246 boost::filesystem::path RLVMInstance::FindGameFile(
247 const boost::filesystem::path& gamerootPath,
248 const std::string& filename) {
249 fs::path search_for = gamerootPath / filename;
250 fs::path corrected_path = CorrectPathCase(search_for);
251 if (corrected_path.empty()) {
252 throw rlvm::UserPresentableError(
253 _("Could not load game"),
254 str(format(_("Could not open %1%. Please make sure it exists.")) %
255 search_for));
256 }
257
258 return corrected_path;
259 }
260
CheckBadEngine(const boost::filesystem::path & gamerootPath,const char ** filenames,const std::string & message_text)261 void RLVMInstance::CheckBadEngine(const boost::filesystem::path& gamerootPath,
262 const char** filenames,
263 const std::string& message_text) {
264 for (const char** cur_file = filenames; *cur_file; cur_file++) {
265 if (fs::exists(CorrectPathCase(gamerootPath / *cur_file))) {
266 throw rlvm::UserPresentableError(message_text,
267 _("rlvm can only play RealLive games."));
268 }
269 }
270 }
271