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 libreallive, a dependency of RLVM.
7 //
8 // -----------------------------------------------------------------------
9 //
10 // Copyright (c) 2006, 2007 Peter Jolly
11 //
12 // Permission is hereby granted, free of charge, to any person
13 // obtaining a copy of this software and associated documentation
14 // files (the "Software"), to deal in the Software without
15 // restriction, including without limitation the rights to use, copy,
16 // modify, merge, publish, distribute, sublicense, and/or sell copies
17 // of the Software, and to permit persons to whom the Software is
18 // furnished to do so, subject to the following conditions:
19 //
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 //
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
27 // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
28 // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
29 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 // SOFTWARE.
31 //
32 // -----------------------------------------------------------------------
33
34 #include "libreallive/archive.h"
35
36 #include <boost/algorithm/string.hpp>
37 #include <boost/filesystem.hpp>
38 #include <cstring>
39 #include <string>
40
41 #include "libreallive/compression.h"
42
43 using boost::istarts_with;
44 using boost::iends_with;
45 namespace fs = boost::filesystem;
46
47 namespace libreallive {
48
Archive(const std::string & filename)49 Archive::Archive(const std::string& filename)
50 : name_(filename), info_(filename, Read), second_level_xor_key_(NULL) {
51 ReadTOC();
52 ReadOverrides();
53 }
54
Archive(const std::string & filename,const std::string & regname)55 Archive::Archive(const std::string& filename, const std::string& regname)
56 : name_(filename),
57 info_(filename, Read),
58 second_level_xor_key_(NULL),
59 regname_(regname) {
60 ReadTOC();
61 ReadOverrides();
62
63 if (regname == "KEY\\CLANNAD_FV") {
64 second_level_xor_key_ =
65 libreallive::compression::clannad_full_voice_xor_mask;
66 } else if (regname ==
67 "\x4b\x45\x59\x5c\x83\x8a\x83\x67\x83\x8b\x83"
68 "\x6f\x83\x58\x83\x5e\x81\x5b\x83\x59\x81\x49") {
69 second_level_xor_key_ = libreallive::compression::little_busters_xor_mask;
70 } else if (regname ==
71 "\x4b\x45\x59\x5c\x83\x8a\x83\x67\x83\x8b\x83\x6f\x83\x58\x83\x5e"
72 "\x81\x5b\x83\x59\x81\x49\x82\x64\x82\x77") {
73 // "KEY\<little busters in katakana>!EX", with all fullwidth latin
74 // characters.
75 second_level_xor_key_ =
76 libreallive::compression::little_busters_ex_xor_mask;
77 } else if (regname == "StudioMebius\\SNOWSE") {
78 second_level_xor_key_ =
79 libreallive::compression::snow_standard_edition_xor_mask;
80 } else if (regname ==
81 "\x4b\x45\x59\x5c\x83\x4e\x83\x68\x82\xed\x82\xd3\x82"
82 "\xbd\x81\x5b") {
83 // "KEY\<Kud Wafter in hiragana>"
84 second_level_xor_key_ =
85 libreallive::compression::kud_wafter_xor_mask;
86 } else if (regname ==
87 "\x4b\x45\x59\x5c\x83\x4e\x83\x68\x82\xed\x82\xd3\x82"
88 "\xbd\x81\x5b\x81\x79\x91\x53\x94\x4e\x97\xee\x91\xce"
89 "\x8f\xdb\x94\xc5\x81\x7a") {
90 second_level_xor_key_ =
91 libreallive::compression::kud_wafter_all_ages_xor_mask;
92 }
93 }
94
~Archive()95 Archive::~Archive() {}
96
GetScenario(int index)97 Scenario* Archive::GetScenario(int index) {
98 accessed_t::const_iterator at = accessed_.find(index);
99 if (at != accessed_.end())
100 return at->second.get();
101 scenarios_t::const_iterator st = scenarios_.find(index);
102 if (st != scenarios_.end()) {
103 Scenario* scene =
104 new Scenario(st->second, index, regname_, second_level_xor_key_);
105 accessed_[index].reset(scene);
106 return scene;
107 }
108 return NULL;
109 }
110
GetProbableEncodingType() const111 int Archive::GetProbableEncodingType() const {
112 // Directly create Header objects instead of Scenarios. We don't want to
113 // parse the entire SEEN file here.
114 for (auto it = scenarios_.cbegin(); it != scenarios_.cend(); ++it) {
115 Header header(it->second.data, it->second.length);
116 if (header.rldev_metadata_.text_encoding() != 0)
117 return header.rldev_metadata_.text_encoding();
118 }
119
120 return 0;
121 }
122
ReadTOC()123 void Archive::ReadTOC() {
124 const char* idx = info_.get();
125 for (int i = 0; i < 10000; ++i, idx += 8) {
126 const int offs = read_i32(idx);
127 if (offs)
128 scenarios_[i] = FilePos(info_.get() + offs, read_i32(idx + 4));
129 }
130 }
131
ReadOverrides()132 void Archive::ReadOverrides() {
133 // Iterate over all files in the directory and override the table of contents
134 // if there is a free SEENXXXX.TXT file.
135 fs::path seen_dir = fs::path(name_).branch_path();
136 fs::directory_iterator end;
137 for (fs::directory_iterator it(seen_dir); it != end; ++it) {
138 std::string filename = it->path().filename().string();
139 if (filename.size() == 12 && istarts_with(filename, "seen") &&
140 iends_with(filename, ".txt") && isdigit(filename[4]) &&
141 isdigit(filename[5]) && isdigit(filename[6]) && isdigit(filename[7])) {
142 Mapping* mapping = new Mapping((seen_dir / filename).string(), Read);
143 maps_to_delete_.emplace_back(mapping);
144
145 int index = std::stoi(filename.substr(4, 4));
146 scenarios_[index] = FilePos(mapping->get(), mapping->size());
147 }
148 }
149 }
150
151 } // namespace libreallive
152