1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "sci/sci.h"
24 #include "sci/engine/state.h"
25 #include "sci/engine/seg_manager.h"
26 #include "sci/engine/vm_types.h"
27 #include "sci/engine/workarounds.h"
28
29 namespace Sci {
30
getSegment() const31 SegmentId reg_t::getSegment() const {
32 if (getSciVersion() < SCI_VERSION_3) {
33 return _segment;
34 } else {
35 // Return the lower 14 bits of the segment
36 return (_segment & 0x3FFF);
37 }
38 }
39
setSegment(SegmentId segment)40 void reg_t::setSegment(SegmentId segment) {
41 if (getSciVersion() < SCI_VERSION_3) {
42 _segment = segment;
43 } else {
44 // Set the lower 14 bits of the segment, and preserve the upper 2 ones for the offset
45 _segment = (_segment & 0xC000) | (segment & 0x3FFF);
46 }
47 }
48
getOffset() const49 uint32 reg_t::getOffset() const {
50 if (getSciVersion() < SCI_VERSION_3) {
51 return _offset;
52 } else {
53 // Return the lower 16 bits from the offset, and the 17th and 18th bits from the segment
54 return ((_segment & 0xC000) << 2) | _offset;
55 }
56 }
57
setOffset(uint32 offset)58 void reg_t::setOffset(uint32 offset) {
59 if (getSciVersion() < SCI_VERSION_3) {
60 _offset = offset;
61 } else {
62 // Store the lower 16 bits in the offset, and the 17th and 18th bits in the segment
63 _offset = offset & 0xFFFF;
64 _segment = ((offset & 0x30000) >> 2) | (_segment & 0x3FFF);
65 }
66 }
67
lookForWorkaround(const reg_t right,const char * operation) const68 reg_t reg_t::lookForWorkaround(const reg_t right, const char *operation) const {
69 SciCallOrigin originReply;
70 SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, arithmeticWorkarounds, &originReply);
71 if (solution.type == WORKAROUND_NONE)
72 error("Invalid arithmetic operation (%s - params: %04x:%04x and %04x:%04x) from %s", operation, PRINT_REG(*this), PRINT_REG(right), originReply.toString().c_str());
73 assert(solution.type == WORKAROUND_FAKE);
74 return make_reg(0, solution.value);
75 }
76
operator +(const reg_t right) const77 reg_t reg_t::operator+(const reg_t right) const {
78 if (isPointer() && right.isNumber()) {
79 // Pointer arithmetics. Only some pointer types make sense here
80 SegmentObj *mobj = g_sci->getEngineState()->_segMan->getSegmentObj(getSegment());
81
82 if (!mobj)
83 error("[VM]: Attempt to add %d to invalid pointer %04x:%04x", right.getOffset(), PRINT_REG(*this));
84
85 switch (mobj->getType()) {
86 case SEG_TYPE_LOCALS:
87 case SEG_TYPE_SCRIPT:
88 case SEG_TYPE_STACK:
89 case SEG_TYPE_DYNMEM:
90 return make_reg(getSegment(), getOffset() + right.toSint16());
91 default:
92 return lookForWorkaround(right, "addition");
93 }
94 } else if (isNumber() && right.isPointer()) {
95 // Adding a pointer to a number, flip the order
96 return right + *this;
97 } else if (isNumber() && right.isNumber()) {
98 // Normal arithmetics
99 return make_reg(0, toSint16() + right.toSint16());
100 } else {
101 return lookForWorkaround(right, "addition");
102 }
103 }
104
operator -(const reg_t right) const105 reg_t reg_t::operator-(const reg_t right) const {
106 if (getSegment() == right.getSegment()) {
107 // We can subtract numbers, or pointers with the same segment,
108 // an operation which will yield a number like in C
109 return make_reg(0, toSint16() - right.toSint16());
110 } else {
111 return *this + make_reg(right.getSegment(), -right.toSint16());
112 }
113 }
114
operator *(const reg_t right) const115 reg_t reg_t::operator*(const reg_t right) const {
116 if (isNumber() && right.isNumber())
117 return make_reg(0, toSint16() * right.toSint16());
118 else
119 return lookForWorkaround(right, "multiplication");
120 }
121
operator /(const reg_t right) const122 reg_t reg_t::operator/(const reg_t right) const {
123 if (isNumber() && right.isNumber() && !right.isNull())
124 return make_reg(0, toSint16() / right.toSint16());
125 else
126 return lookForWorkaround(right, "division");
127 }
128
operator %(const reg_t right) const129 reg_t reg_t::operator%(const reg_t right) const {
130 if (isNumber() && right.isNumber() && !right.isNull()) {
131 // Support for negative numbers was added in Iceman, and perhaps in
132 // SCI0 0.000.685 and later. Theoretically, this wasn't really used
133 // in SCI0, so the result is probably unpredictable. Such a case
134 // would indicate either a script bug, or a modulo on an unsigned
135 // integer larger than 32767. In any case, such a case should be
136 // investigated, instead of being silently accepted.
137 if (getSciVersion() <= SCI_VERSION_0_LATE && (toSint16() < 0 || right.toSint16() < 0))
138 warning("Modulo of a negative number has been requested for SCI0. This *could* lead to issues");
139 int16 value = toSint16();
140 int16 modulo = ABS(right.toSint16());
141 int16 result = value % modulo;
142 if (result < 0)
143 result += modulo;
144 return make_reg(0, result);
145 } else
146 return lookForWorkaround(right, "modulo");
147 }
148
operator >>(const reg_t right) const149 reg_t reg_t::operator>>(const reg_t right) const {
150 if (isNumber() && right.isNumber())
151 return make_reg(0, toUint16() >> right.toUint16());
152 else
153 return lookForWorkaround(right, "shift right");
154 }
155
operator <<(const reg_t right) const156 reg_t reg_t::operator<<(const reg_t right) const {
157 if (isNumber() && right.isNumber())
158 return make_reg(0, toUint16() << right.toUint16());
159 else
160 return lookForWorkaround(right, "shift left");
161 }
162
operator +(int16 right) const163 reg_t reg_t::operator+(int16 right) const {
164 return *this + make_reg(0, right);
165 }
166
operator -(int16 right) const167 reg_t reg_t::operator-(int16 right) const {
168 return *this - make_reg(0, right);
169 }
170
requireUint16() const171 uint16 reg_t::requireUint16() const {
172 if (isNumber())
173 return toUint16();
174 else
175 // The right parameter is NULL_REG because
176 // we're not comparing *this with anything here.
177 return lookForWorkaround(NULL_REG, "require unsigned number").toUint16();
178 }
179
requireSint16() const180 int16 reg_t::requireSint16() const {
181 if (isNumber())
182 return toSint16();
183 else
184 // The right parameter is NULL_REG because
185 // we're not comparing *this with anything here.
186 return lookForWorkaround(NULL_REG, "require signed number").toSint16();
187 }
188
operator &(const reg_t right) const189 reg_t reg_t::operator&(const reg_t right) const {
190 if (isNumber() && right.isNumber())
191 return make_reg(0, toUint16() & right.toUint16());
192 else
193 return lookForWorkaround(right, "bitwise AND");
194 }
195
operator |(const reg_t right) const196 reg_t reg_t::operator|(const reg_t right) const {
197 if (isNumber() && right.isNumber())
198 return make_reg(0, toUint16() | right.toUint16());
199 else
200 return lookForWorkaround(right, "bitwise OR");
201 }
202
operator ^(const reg_t right) const203 reg_t reg_t::operator^(const reg_t right) const {
204 if (isNumber() && right.isNumber())
205 return make_reg(0, toUint16() ^ right.toUint16());
206 else
207 return lookForWorkaround(right, "bitwise XOR");
208 }
209
210 #ifdef ENABLE_SCI32
operator &(int16 right) const211 reg_t reg_t::operator&(int16 right) const {
212 return *this & make_reg(0, right);
213 }
214
operator |(int16 right) const215 reg_t reg_t::operator|(int16 right) const {
216 return *this | make_reg(0, right);
217 }
218
operator ^(int16 right) const219 reg_t reg_t::operator^(int16 right) const {
220 return *this ^ make_reg(0, right);
221 }
222 #endif
223
cmp(const reg_t right,bool treatAsUnsigned) const224 int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const {
225 if (getSegment() == right.getSegment()) { // can compare things in the same segment
226 if (treatAsUnsigned || !isNumber())
227 return toUint16() - right.toUint16();
228 else
229 return toSint16() - right.toSint16();
230 #ifdef ENABLE_SCI32
231 } else if (getSciVersion() >= SCI_VERSION_2) {
232 return sci32Comparison(right);
233 #endif
234 } else if (pointerComparisonWithInteger(right)) {
235 return 1;
236 } else if (right.pointerComparisonWithInteger(*this)) {
237 return -1;
238 } else
239 return lookForWorkaround(right, "comparison").toSint16();
240 }
241
242 #ifdef ENABLE_SCI32
sci32Comparison(const reg_t right) const243 int reg_t::sci32Comparison(const reg_t right) const {
244 // In SCI32, MemIDs are normally indexes into the memory manager's handle
245 // list, but the engine reserves indexes at and above 20000 for objects
246 // that were created inside the engine (as opposed to inside the VM). The
247 // engine compares these as a tiebreaker for graphics objects that are at
248 // the same priority, and it is necessary to at least minimally handle
249 // this situation.
250 // This is obviously a bogus comparison, but then, this entire thing is
251 // bogus. For the moment, it just needs to be deterministic.
252 if (isNumber() && !right.isNumber()) {
253 return 1;
254 } else if (right.isNumber() && !isNumber()) {
255 return -1;
256 }
257
258 return getOffset() - right.getOffset();
259 }
260 #endif
261
pointerComparisonWithInteger(const reg_t right) const262 bool reg_t::pointerComparisonWithInteger(const reg_t right) const {
263 // This function handles the case where a script tries to compare a pointer
264 // to a number. Normally, we would not want to allow that. However, SCI0 -
265 // SCI1.1 scripts do this in order to distinguish references to
266 // external resources (which are numbers) from pointers. In
267 // our SCI implementation, such a check may seem pointless, as
268 // one can simply use the segment value to achieve this goal.
269 // But Sierra's SCI did not have the notion of segment IDs, so
270 // both pointer and numbers were simple integers.
271 //
272 // But for some things, scripts had (and have) to distinguish between
273 // numbers and pointers. Lacking the segment information, Sierra's
274 // developers resorted to a hack: If an integer is smaller than a certain
275 // bound, it can be assumed to be a number, otherwise it is assumed to be a
276 // pointer. This allowed them to implement polymorphic functions, such as
277 // the Print function, which can be called in two different ways, with a
278 // pointer or a far text reference:
279 //
280 // (Print "foo") // Pointer to a string
281 // (Print 420 5) // Reference to the fifth message in text resource 420
282 // It works because in those games, the maximum resource number is 999,
283 // so any parameter value above that threshold must be a pointer.
284 // PQ2 japanese compares pointers to 2000 to find out if its a pointer
285 // or a resource ID. Thus, we check for all integers <= 2000.
286 //
287 // Some examples where game scripts check for arbitrary numbers against
288 // pointers:
289 // Hoyle 3, Pachisi, when any opponent is about to talk
290 // SQ1, room 28, when throwing water at the Orat
291 // SQ1, room 58, when giving the ID card to the robot
292 // SQ4 CD, at the first game screen, when the narrator is about to speak
293 return (isPointer() && right.isNumber() && right.getOffset() <= 2000 && getSciVersion() <= SCI_VERSION_1_1);
294 }
295
296 } // End of namespace Sci
297