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