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) 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 // This code is heavily based off Haeleth's O'caml implementation
29 // (which translates binary GAN files to and from an XML
30 // representation), found at rldev/src/rlxml/gan.ml.
31 
32 #include <boost/archive/text_oarchive.hpp>
33 #include <boost/archive/text_iarchive.hpp>
34 
35 #include "systems/base/gan_graphics_object_data.h"
36 
37 #include <boost/serialization/export.hpp>
38 #include <boost/filesystem/fstream.hpp>
39 #include <iostream>
40 #include <string>
41 #include <vector>
42 
43 #include "libreallive/defs.h"
44 #include "machine/serialization.h"
45 #include "systems/base/event_system.h"
46 #include "systems/base/graphics_object.h"
47 #include "systems/base/graphics_system.h"
48 #include "systems/base/surface.h"
49 #include "systems/base/system.h"
50 #include "utilities/exception.h"
51 #include "utilities/file.h"
52 
53 using libreallive::read_i32;
54 using std::string;
55 using std::ifstream;
56 using std::ostringstream;
57 using std::cerr;
58 using std::endl;
59 using std::vector;
60 
61 namespace fs = boost::filesystem;
62 
63 // -----------------------------------------------------------------------
64 // GanGraphicsObjectData
65 // -----------------------------------------------------------------------
66 
GanGraphicsObjectData(System & system)67 GanGraphicsObjectData::GanGraphicsObjectData(System& system)
68     : system_(system),
69       current_set_(-1),
70       current_frame_(-1),
71       time_at_last_frame_change_(0) {}
72 
GanGraphicsObjectData(System & system,const std::string & gan_file,const std::string & img_file)73 GanGraphicsObjectData::GanGraphicsObjectData(System& system,
74                                              const std::string& gan_file,
75                                              const std::string& img_file)
76     : system_(system),
77       gan_filename_(gan_file),
78       img_filename_(img_file),
79       current_set_(-1),
80       current_frame_(-1),
81       time_at_last_frame_change_(0) {
82   LoadGANData();
83 }
84 
~GanGraphicsObjectData()85 GanGraphicsObjectData::~GanGraphicsObjectData() {}
86 
LoadGANData()87 void GanGraphicsObjectData::LoadGANData() {
88   image_ = system_.graphics().GetSurfaceNamed(img_filename_);
89   image_->EnsureUploaded();
90 
91   fs::path gan_file_path = system_.FindFile(gan_filename_, GAN_FILETYPES);
92   if (gan_file_path.empty()) {
93     ostringstream oss;
94     oss << "Could not find GAN file \"" << gan_filename_ << "\".";
95     throw rlvm::Exception(oss.str());
96   }
97 
98   int file_size = 0;
99   std::unique_ptr<char[]> gan_data;
100   if (LoadFileData(gan_file_path, gan_data, file_size)) {
101     ostringstream oss;
102     oss << "Could not read the contents of \"" << gan_file_path << "\"";
103     throw rlvm::Exception(oss.str());
104   }
105 
106   TestFileMagic(gan_filename_, gan_data, file_size);
107   ReadData(gan_filename_, gan_data, file_size);
108 }
109 
TestFileMagic(const std::string & file_name,std::unique_ptr<char[]> & gan_data,int file_size)110 void GanGraphicsObjectData::TestFileMagic(const std::string& file_name,
111                                           std::unique_ptr<char[]>& gan_data,
112                                           int file_size) {
113   const char* data = gan_data.get();
114   int a = read_i32(data);
115   int b = read_i32(data + 0x04);
116   int c = read_i32(data + 0x08);
117 
118   if (a != 10000 || b != 10000 || c != 10100)
119     ThrowBadFormat(file_name, "Incorrect GAN file magic");
120 }
121 
ReadData(const std::string & file_name,std::unique_ptr<char[]> & gan_data,int file_size)122 void GanGraphicsObjectData::ReadData(const std::string& file_name,
123                                      std::unique_ptr<char[]>& gan_data,
124                                      int file_size) {
125   const char* data = gan_data.get();
126   int file_name_length = read_i32(data + 0xc);
127   string raw_file_name = data + 0x10;
128 
129   // Strings should be NULL terminated.
130   data = data + 0x10 + file_name_length - 1;
131   if (*data != 0)
132     ThrowBadFormat(file_name, "Incorrect filename length in GAN header");
133   data++;
134 
135   int twenty_thousand = read_i32(data);
136   if (twenty_thousand != 20000)
137     ThrowBadFormat(file_name, "Expected start of GAN data section");
138   data += 4;
139 
140   int number_of_sets = read_i32(data);
141   data += 4;
142 
143   for (int i = 0; i < number_of_sets; ++i) {
144     int start_of_ganset = read_i32(data);
145     if (start_of_ganset != 0x7530)
146       ThrowBadFormat(file_name, "Expected start of GAN set");
147 
148     data += 4;
149     int frame_count = read_i32(data);
150     if (frame_count < 0)
151       ThrowBadFormat(file_name,
152                      "Expected animation to contain at least one frame");
153     data += 4;
154 
155     vector<Frame> animation_set;
156     for (int j = 0; j < frame_count; ++j)
157       animation_set.push_back(ReadSetFrame(file_name, data));
158     animation_sets.push_back(animation_set);
159   }
160 }
161 
ReadSetFrame(const std::string & file_name,const char * & data)162 GanGraphicsObjectData::Frame GanGraphicsObjectData::ReadSetFrame(
163     const std::string& file_name,
164     const char*& data) {
165   GanGraphicsObjectData::Frame frame;
166 
167   int tag = read_i32(data);
168   data += 4;
169   while (tag != 999999) {
170     int value = read_i32(data);
171     data += 4;
172 
173     switch (tag) {
174       case 30100:
175         frame.pattern = value;
176         break;
177       case 30101:
178         frame.x = value;
179         break;
180       case 30102:
181         frame.y = value;
182         break;
183       case 30103:
184         frame.time = value;
185         break;
186       case 30104:
187         frame.alpha = value;
188         break;
189       case 30105:
190         frame.other = value;
191         break;
192       default: {
193         ostringstream oss;
194         oss << "Unknown GAN frame tag: " << tag;
195         ThrowBadFormat(file_name, oss.str());
196       }
197     }
198 
199     tag = read_i32(data);
200     data += 4;
201   }
202 
203   return frame;
204 }
205 
ThrowBadFormat(const std::string & file_name,const std::string & error)206 void GanGraphicsObjectData::ThrowBadFormat(const std::string& file_name,
207                                            const std::string& error) {
208   ostringstream oss;
209   oss << "File \"" << file_name
210       << "\" does not appear to be in GAN format: " << error;
211   throw rlvm::Exception(oss.str());
212 }
213 
PixelWidth(const GraphicsObject & rendering_properties)214 int GanGraphicsObjectData::PixelWidth(
215     const GraphicsObject& rendering_properties) {
216   if (current_set_ != -1 && current_frame_ != -1) {
217     const Frame& frame = animation_sets.at(current_set_).at(current_frame_);
218     if (frame.pattern != -1) {
219       const Surface::GrpRect& rect = image_->GetPattern(frame.pattern);
220       return int(rendering_properties.GetWidthScaleFactor() *
221                  rect.rect.width());
222     }
223   }
224 
225   return 0;
226 }
227 
PixelHeight(const GraphicsObject & rendering_properties)228 int GanGraphicsObjectData::PixelHeight(
229     const GraphicsObject& rendering_properties) {
230   if (current_set_ != -1 && current_frame_ != -1) {
231     const Frame& frame = animation_sets.at(current_set_).at(current_frame_);
232     if (frame.pattern != -1) {
233       const Surface::GrpRect& rect = image_->GetPattern(frame.pattern);
234       return int(rendering_properties.GetHeightScaleFactor() *
235                  rect.rect.height());
236     }
237   }
238 
239   return 0;
240 }
241 
Clone() const242 GraphicsObjectData* GanGraphicsObjectData::Clone() const {
243   return new GanGraphicsObjectData(*this);
244 }
245 
Execute(RLMachine & machine)246 void GanGraphicsObjectData::Execute(RLMachine& machine) {
247   if (is_currently_playing() && current_frame_ >= 0) {
248     unsigned int current_time = system_.event().GetTicks();
249     unsigned int time_since_last_frame_change =
250         current_time - time_at_last_frame_change_;
251 
252     const vector<Frame>& current_set = animation_sets.at(current_set_);
253     unsigned int frame_time = (unsigned int)(current_set[current_frame_].time);
254     if (time_since_last_frame_change > frame_time) {
255       current_frame_++;
256       if (size_t(current_frame_) == current_set.size()) {
257         current_frame_--;
258         // endAnimation() can delete this, so it needs to be the last thing
259         // done in this code path...
260         EndAnimation();
261       } else {
262         time_at_last_frame_change_ = current_time;
263         system_.graphics().MarkScreenAsDirty(GUT_DISPLAY_OBJ);
264       }
265     }
266   }
267 }
268 
LoopAnimation()269 void GanGraphicsObjectData::LoopAnimation() { current_frame_ = 0; }
270 
CurrentSurface(const GraphicsObject & go)271 std::shared_ptr<const Surface> GanGraphicsObjectData::CurrentSurface(
272     const GraphicsObject& go) {
273   if (current_set_ != -1 && current_frame_ != -1) {
274     const Frame& frame = animation_sets.at(current_set_).at(current_frame_);
275 
276     if (frame.pattern != -1) {
277       // We are currently rendering an animation AND the current frame says to
278       // render something to the screen.
279       return image_;
280     }
281   }
282 
283   return std::shared_ptr<const Surface>();
284 }
285 
SrcRect(const GraphicsObject & go)286 Rect GanGraphicsObjectData::SrcRect(const GraphicsObject& go) {
287   const Frame& frame = animation_sets.at(current_set_).at(current_frame_);
288   if (frame.pattern != -1) {
289     return image_->GetPattern(frame.pattern).rect;
290   }
291 
292   return Rect();
293 }
294 
DstOrigin(const GraphicsObject & go)295 Point GanGraphicsObjectData::DstOrigin(const GraphicsObject& go) {
296   const Frame& frame = animation_sets.at(current_set_).at(current_frame_);
297   return GraphicsObjectData::DstOrigin(go) - Size(frame.x, frame.y);
298 }
299 
GetRenderingAlpha(const GraphicsObject & go,const GraphicsObject * parent)300 int GanGraphicsObjectData::GetRenderingAlpha(const GraphicsObject& go,
301                                              const GraphicsObject* parent) {
302   const Frame& frame = animation_sets.at(current_set_).at(current_frame_);
303   if (frame.pattern != -1) {
304     // Calculate the combination of our frame alpha with the current object
305     // alpha.
306     float parent_alpha = parent ? (parent->GetComputedAlpha() / 255.0f) : 1;
307     return int(((frame.alpha / 255.0f) * (go.GetComputedAlpha() / 255.0f) *
308                 parent_alpha) *
309                255);
310   } else {
311     // Should never happen.
312     return go.GetComputedAlpha();
313   }
314 }
315 
ObjectInfo(std::ostream & tree)316 void GanGraphicsObjectData::ObjectInfo(std::ostream& tree) {
317   tree << "  GAN file: " << gan_filename_ << " (Using image: " << img_filename_
318        << ")" << endl;
319 }
320 
PlaySet(int set)321 void GanGraphicsObjectData::PlaySet(int set) {
322   set_is_currently_playing(true);
323   current_set_ = set;
324   current_frame_ = 0;
325   time_at_last_frame_change_ = system_.event().GetTicks();
326   system_.graphics().MarkScreenAsDirty(GUT_DISPLAY_OBJ);
327 }
328 
329 template <class Archive>
load(Archive & ar,unsigned int version)330 void GanGraphicsObjectData::load(Archive& ar, unsigned int version) {
331   ar& boost::serialization::base_object<GraphicsObjectData>(*this) &
332       gan_filename_ & img_filename_ & current_set_ & current_frame_ &
333       time_at_last_frame_change_;
334 
335   LoadGANData();
336 
337   // Saving |time_at_last_frame_change_| as part of the format is obviously a
338   // mistake, but is now baked into the file format. Ask the clock for a more
339   // suitable value.
340   if (time_at_last_frame_change_ != 0) {
341     time_at_last_frame_change_ = system_.event().GetTicks();
342     system_.graphics().MarkScreenAsDirty(GUT_DISPLAY_OBJ);
343   }
344 }
345 
346 template <class Archive>
save(Archive & ar,unsigned int version) const347 void GanGraphicsObjectData::save(Archive& ar, unsigned int version) const {
348   ar& boost::serialization::base_object<GraphicsObjectData>(*this) &
349       gan_filename_ & img_filename_ & current_set_ & current_frame_ &
350       time_at_last_frame_change_;
351 }
352 
353 // -----------------------------------------------------------------------
354 
355 // Explicit instantiations for text archives (since we hide the
356 // implementation)
357 
358 template void GanGraphicsObjectData::save<boost::archive::text_oarchive>(
359     boost::archive::text_oarchive& ar,
360     unsigned int version) const;
361 
362 template void GanGraphicsObjectData::load<boost::archive::text_iarchive>(
363     boost::archive::text_iarchive& ar,
364     unsigned int version);
365 
366 // -----------------------------------------------------------------------
367 
368 BOOST_CLASS_EXPORT(GanGraphicsObjectData);
369