1 /** @file module.cpp Action Code Script (ACS) module.
2 *
3 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright © 2005-2015 Daniel Swanson <danij@dengine.net>
5 * @authors Copyright © 1999 Activision
6 *
7 * @par License
8 * GPL: http://www.gnu.org/licenses/gpl.html
9 *
10 * <small>This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by the
12 * Free Software Foundation; either version 2 of the License, or (at your
13 * option) any later version. This program is distributed in the hope that it
14 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
15 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16 * Public License for more details. You should have received a copy of the GNU
17 * General Public License along with this program; if not, write to the Free
18 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19 * 02110-1301 USA</small>
20 */
21
22 #include "common.h" // IS_CLIENT
23 #include "acs/module.h"
24
25 #include <QList>
26 #include <QMap>
27 #include <QVector>
28 #include <de/Log>
29 #include "acs/interpreter.h" // ACS_INTERPRETER_MAX_SCRIPT_ARGS
30 #include "gamesession.h"
31
32 using namespace de;
33
34 namespace acs {
35
DENG2_PIMPL_NOREF(Module)36 DENG2_PIMPL_NOREF(Module)
37 {
38 Block pcode;
39 QVector<EntryPoint> entryPoints;
40 QMap<int, EntryPoint *> epByScriptNumberLut;
41 QList<String> constants;
42
43 void buildEntryPointLut()
44 {
45 epByScriptNumberLut.clear();
46 for(auto &ep : entryPoints)
47 {
48 epByScriptNumberLut.insert(ep.scriptNumber, &ep);
49 }
50 }
51 };
52
Module()53 Module::Module() : d(new Impl)
54 {}
55
recognize(File1 const & file)56 bool Module::recognize(File1 const &file) // static
57 {
58 if(file.size() <= 4) return false;
59
60 // ACS bytecode begins with the magic identifier "ACS".
61 Block magic(4);
62 const_cast<File1 &>(file).read(magic.data(), 0, 4);
63 if(!magic.startsWith("ACS")) return false;
64
65 // ZDoom uses the fourth byte for versioning of their extended formats.
66 // Currently such formats are not supported.
67 return magic.at(3) == 0;
68 }
69
newFromBytecode(Block const & bytecode)70 Module *Module::newFromBytecode(Block const &bytecode) // static
71 {
72 DENG2_ASSERT(!IS_CLIENT);
73 LOG_AS("acs::Module");
74
75 std::unique_ptr<Module> module(new Module);
76
77 // Copy the complete bytecode data into a local buffer (we'll be randomly
78 // accessing this frequently).
79 module->d->pcode = bytecode;
80
81 de::Reader from(module->d->pcode);
82 dint32 magic, scriptInfoOffset;
83 from >> magic >> scriptInfoOffset;
84
85 // Read script entry point info.
86 from.seek(dint(scriptInfoOffset) - dint(from.offset()));
87 dint32 numEntryPoints;
88 from >> numEntryPoints;
89 module->d->entryPoints.reserve(numEntryPoints);
90 for(dint32 i = 0; i < numEntryPoints; ++i)
91 {
92 #define OPEN_SCRIPTS_BASE 1000
93
94 EntryPoint ep;
95
96 from >> ep.scriptNumber;
97 if(ep.scriptNumber >= OPEN_SCRIPTS_BASE)
98 {
99 ep.scriptNumber -= OPEN_SCRIPTS_BASE;
100 ep.startWhenMapBegins = true;
101 }
102
103 dint32 offset;
104 from >> offset;
105 if(offset > dint32(module->d->pcode.size()))
106 {
107 throw FormatError("acs::Module", "Invalid script entrypoint offset");
108 }
109 ep.pcodePtr = (int const *)(module->d->pcode.constData() + offset);
110
111 from >> ep.scriptArgCount;
112 if(ep.scriptArgCount > ACS_INTERPRETER_MAX_SCRIPT_ARGS)
113 {
114 throw FormatError("acs::Module", "Too many script arguments (" + String::number(ep.scriptArgCount) + " > " + String::number(ACS_INTERPRETER_MAX_SCRIPT_ARGS) + ")");
115 }
116
117 module->d->entryPoints << ep; // makes a copy.
118
119 #undef OPEN_SCRIPTS_BASE
120 }
121 // Prepare a script-number => EntryPoint LUT.
122 module->d->buildEntryPointLut();
123
124 // Read constant (string-)values.
125 dint32 numConstants;
126 from >> numConstants;
127 QVector<dint32> constantOffsets;
128 constantOffsets.reserve(numConstants);
129 for(dint32 i = 0; i < numConstants; ++i)
130 {
131 dint32 offset;
132 from >> offset;
133 constantOffsets << offset;
134 }
135 for(auto const &offset : constantOffsets)
136 {
137 Block utf;
138 from.setOffset(dsize(offset));
139 from.readUntil(utf, 0);
140 module->d->constants << String::fromUtf8(utf);
141 }
142
143 return module.release();
144 }
145
newFromFile(File1 const & file)146 Module *Module::newFromFile(File1 const &file) // static
147 {
148 DENG2_ASSERT(!IS_CLIENT);
149 LOG_AS("acs::Module");
150 LOG_SCR_VERBOSE("Loading from %s:%s...")
151 << NativePath(file.container().composePath()).pretty()
152 << file.name();
153
154 // Buffer the whole file.
155 Block buffer(file.size());
156 const_cast<File1 &>(file).read(buffer.data());
157
158 return newFromBytecode(buffer);
159 }
160
constant(int stringNumber) const161 String Module::constant(int stringNumber) const
162 {
163 if(stringNumber >= 0 && stringNumber < d->constants.count())
164 {
165 return d->constants[stringNumber];
166 }
167 /// @throw MissingConstantError Invalid constant (string-)value number specified.
168 throw MissingConstantError("acs::Module::constant", "Unknown constant #" + String::number(stringNumber));
169 }
170
entryPointCount() const171 int Module::entryPointCount() const
172 {
173 return d->entryPoints.count();
174 }
175
hasEntryPoint(int scriptNumber) const176 bool Module::hasEntryPoint(int scriptNumber) const
177 {
178 return d->epByScriptNumberLut.contains(scriptNumber);
179 }
180
entryPoint(int scriptNumber) const181 Module::EntryPoint const &Module::entryPoint(int scriptNumber) const
182 {
183 if(hasEntryPoint(scriptNumber)) return *d->epByScriptNumberLut[scriptNumber];
184 /// @throw MissingEntryPointError Invalid script number specified.
185 throw MissingEntryPointError("acs::Module::entryPoint", "Unknown script #" + String::number(scriptNumber));
186 }
187
forAllEntryPoints(std::function<LoopResult (EntryPoint &)> func) const188 LoopResult Module::forAllEntryPoints(std::function<LoopResult (EntryPoint &)> func) const
189 {
190 for(EntryPoint &ep : d->entryPoints)
191 {
192 if(auto result = func(ep)) return result;
193 }
194 return LoopContinue;
195 }
196
pcode() const197 Block const &Module::pcode() const
198 {
199 return d->pcode;
200 }
201
202 } // namespace acs
203