1 /* ScummVM Tools
2 *
3 * ScummVM Tools is the legal property of its developers, whose
4 * names are too numerous to list here. Please refer to the
5 * COPYRIGHT 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 /* Scumm Script Disassembler (common code) */
23
24 #include <string.h>
25 #include <stdio.h>
26
27 #include "descumm.h"
28
29 #include "common/endian.h"
30
31 BlockStack g_blockStack;
32
33 bool pendingElse, haveElse;
34 int pendingElseTo;
35 int pendingElseOffs;
36 int pendingElseOpcode;
37 int pendingElseIndent;
38
39 int g_jump_opcode;
40
41 Options g_options;
42
43 byte *g_scriptCurPos, *g_scriptStart;
44 int currentOpcodeBlockStart;
45
46 uint g_scriptSize;
47
48
49 ///////////////////////////////////////////////////////////////////////////
50
strecpy(char * buf,const char * src)51 char *strecpy(char *buf, const char *src) {
52 strcpy(buf, src);
53 return strchr(buf, 0);
54 }
55
get_curoffs()56 int get_curoffs() {
57 return g_scriptCurPos - g_scriptStart;
58 }
59
get_byte()60 int get_byte() {
61 return (byte)(*g_scriptCurPos++);
62 }
63
get_word()64 int get_word() {
65 int i;
66
67 if (g_options.scriptVersion == 8) {
68 i = (int32)READ_LE_UINT32(g_scriptCurPos);
69 g_scriptCurPos += 4;
70 } else {
71 i = (int16)READ_LE_UINT16(g_scriptCurPos);
72 g_scriptCurPos += 2;
73 }
74 return i;
75 }
76
get_dword()77 int get_dword() {
78 int i;
79
80 i = (int32)READ_LE_UINT32(g_scriptCurPos);
81 g_scriptCurPos += 4;
82 return i;
83 }
84
85
86 ///////////////////////////////////////////////////////////////////////////
87
outputLine(const char * buf,int curoffs,int opcode,int indent)88 void outputLine(const char *buf, int curoffs, int opcode, int indent) {
89
90 if (buf[0]) {
91 assert(curoffs >= 0);
92 assert(indent >= 0);
93
94 // Show the offset
95 if (!g_options.dontShowOffsets) {
96 printf("[%.4X] ", curoffs);
97 }
98
99 // Show the opcode value
100 if (!g_options.dontShowOpcode) {
101 if (opcode != -1)
102 printf("(%.2X) ", opcode);
103 else
104 printf("(**) ");
105 }
106
107 // Indent the line as requested ...
108 for (int i = 0; i < indent; ++i)
109 printf(" ");
110
111 // ... and finally print the actual code
112 puts(buf);
113 }
114 }
115
116 ///////////////////////////////////////////////////////////////////////////
117
118 // Returns 0 or 1 depending if it's ok to add a block
maybeAddIf(uint cur,uint to)119 bool maybeAddIf(uint cur, uint to) {
120 Block p;
121 int i;
122
123 if (((to | cur) >> 24) || (to <= cur))
124 return false; // Invalid jump
125
126 for (i = 0; i < g_blockStack.size(); ++i) {
127 if (to > g_blockStack[i].to)
128 return false;
129 }
130
131 // Try to determine if this is a while loop. For this, first check if we
132 // jump right behind a regular jump, then whether that jump is targeting us.
133 if (g_options.scriptVersion == 8) {
134 p.isWhile = (*(byte*)(g_scriptStart+to-5) == g_jump_opcode);
135 i = (int32)READ_LE_UINT32(g_scriptStart+to-4);
136 } else {
137 p.isWhile = (*(byte*)(g_scriptStart+to-3) == g_jump_opcode);
138 i = (int16)READ_LE_UINT16(g_scriptStart+to-2);
139 }
140
141 p.isWhile = p.isWhile && (currentOpcodeBlockStart == (int)to + i);
142 p.from = cur;
143 p.to = to;
144
145 g_blockStack.push(p);
146
147 return true;
148 }
149
150 // Returns 0 or 1 depending if it's ok to add an else
maybeAddElse(uint cur,uint to)151 bool maybeAddElse(uint cur, uint to) {
152 int i;
153
154 if (((to | cur) >> 16) || (to <= cur))
155 return false; /* Invalid jump */
156
157 if (g_blockStack.empty())
158 return false; /* There are no previous blocks, so an else is not ok */
159
160 if (cur != g_blockStack.top().to)
161 return false; /* We have no prevoius if that is exiting right at the end of this goto */
162
163 // Don't jump out of previous blocks. In addition, don't jump "onto"
164 // the end of a while loop, as that would lead to incorrect output.
165 // This test is stronger than the one in maybeAddIf.
166 for (i = 0; i < g_blockStack.size() - 1; ++i) {
167 if (to > g_blockStack[i].to || (to == g_blockStack[i].to && g_blockStack[i].isWhile))
168 return false;
169 }
170
171 Block tmp = g_blockStack.pop();
172 if (maybeAddIf(cur, to))
173 return true; /* We can add an else */
174 g_blockStack.push(tmp);
175 return false; /* An else is not OK here :( */
176 }
177
maybeAddElseIf(uint cur,uint elseto,uint to)178 bool maybeAddElseIf(uint cur, uint elseto, uint to) {
179 uint k;
180
181 if (((to | cur | elseto) >> 16) || (elseto < to) || (to <= cur))
182 return false; /* Invalid jump */
183
184 if (g_blockStack.empty())
185 return false; /* There are no previous blocks, so an ifelse is not ok */
186
187 if (g_blockStack.top().isWhile)
188 return false;
189
190 if (g_options.scriptVersion == 8)
191 k = to - 5;
192 else
193 k = to - 3;
194
195 if (k >= g_scriptSize)
196 return false; /* Invalid jump */
197
198 if (elseto != to) {
199 if (g_scriptStart[k] != g_jump_opcode)
200 return false; /* Invalid jump */
201
202 if (g_options.scriptVersion == 8)
203 k = to + READ_LE_UINT32(g_scriptStart + k + 1);
204 else
205 k = to + READ_LE_UINT16(g_scriptStart + k + 1);
206
207 if (k != elseto)
208 return false; /* Not an ifelse */
209 }
210 g_blockStack.top().from = cur;
211 g_blockStack.top().to = to;
212
213 return true;
214 }
215
maybeAddBreak(uint cur,uint to)216 bool maybeAddBreak(uint cur, uint to) {
217 if (((to | cur) >> 16) || (to <= cur))
218 return false; /* Invalid jump */
219
220 if (g_blockStack.empty())
221 return false; /* There are no previous blocks, so a break is not ok */
222
223 /* Find the first parent block that is a while and if we're jumping to the end of that, we use a break */
224 for (int i = g_blockStack.size() - 1; i >= 0; --i) {
225 if (g_blockStack[i].isWhile) {
226 if (to == g_blockStack[i].to)
227 return true;
228 else
229 return false;
230 }
231 }
232
233 return false;
234 }
235
writePendingElse()236 void writePendingElse() {
237 if (pendingElse) {
238 char buf[32];
239 sprintf(buf, g_options.alwaysShowOffs ? "} else /*%.4X*/ {" : "} else {", pendingElseTo);
240 outputLine(buf, currentOpcodeBlockStart, pendingElseOpcode, pendingElseIndent - 1);
241 currentOpcodeBlockStart = pendingElseOffs;
242 pendingElse = false;
243 }
244 }
245
put_ascii(char * buf,int i)246 char *put_ascii(char *buf, int i) {
247 if (i > 31 && i < 128) {
248 // non-printable chars are escaped by backslashes as so: "\x00"
249 // backslashes and quote marks are escaped like so: "\\" "\""
250 if (i == '\\' || i == '"') {
251 buf[0] = '\\';
252 buf++;
253 }
254 buf[0] = i;
255 buf[1] = 0;
256 return buf + 1;
257 }
258 return buf + sprintf(buf, "\\x%.2X", i);
259 }
260
261 extern char *get_var(char *buf);
262 extern char *get_var6(char *buf);
263
get_string(char * buf)264 char *get_string(char *buf) {
265 byte cmd;
266 char *e = buf;
267 bool in = false;
268 bool in_function = false;
269 int i;
270
271 while ((cmd = get_byte()) != 0) {
272 if (cmd == 0xFF || cmd == 0xFE) {
273 if (in) {
274 e += sprintf(e, "\" + ");
275 in = false;
276 }
277 in_function = true;
278 i = get_byte();
279 switch (i) {
280 case 1: // newline
281 e += sprintf(e, "newline()");
282 break;
283 case 2:
284 e += sprintf(e, "keepText()");
285 break;
286 case 3:
287 e += sprintf(e, "wait()");
288 break;
289 case 4: // addIntToStack
290 e += sprintf(e, "getInt(");
291 goto addVarToStack;
292 case 5: // addVerbToStack
293 e += sprintf(e, "getVerb(");
294 goto addVarToStack;
295 case 6: // addNameToStack
296 e += sprintf(e, "getName(");
297 goto addVarToStack;
298 case 7: // addStringToStack
299 e += sprintf(e, "getString(");
300 addVarToStack:
301 if (g_options.scriptVersion >= 6) {
302 e = get_var6(e);
303 } else {
304 e = get_var(e);
305 }
306 e += sprintf(e, ")");
307 break;
308 case 9:
309 e += sprintf(e, "startAnim(%d)", get_word());
310 break;
311 case 10:
312 e += sprintf(e, "sound(");
313 // positions 2, 3, 6, 7 are the offset in MONSTER.SOU (LE).
314 // positions 10, 11, 14, 15 are the VCTL block size (LE).
315 {
316 // show the voice's position in the MONSTER.SOU
317 unsigned int p = 0;
318 p += get_word() & 0xFFFF;
319 g_scriptCurPos += 2; // skip the next "0xFF 0x0A"
320 p += (get_word() & 0xFFFF) << 16;
321 e += sprintf(e, "0x%X, ", p);
322
323 g_scriptCurPos += 2; // skip the next "0xFF 0x0A"
324
325 // show the size of the VCTL chunk/lip-synch tags
326 p = 0;
327 p += get_word() & 0xFFFF;
328 g_scriptCurPos += 2; // skip the next "0xFF 0x0A"
329 p += (get_word() & 0xFFFF) << 16;
330 e += sprintf(e, "0x%X)", p);
331 }
332 break;
333 case 12:
334 e += sprintf(e, "setColor(%d)", get_word());
335 break;
336 case 13: // was unk2
337 e += sprintf(e, "unknown13(%d)", get_word());
338 break;
339 case 14:
340 e += sprintf(e, "setFont(%d)", get_word());
341 break;
342 case 32: // Workaround for a script bug in Indy3
343 case 46: // Workaround for a script bug in Indy3
344 if (g_options.scriptVersion == 3 && g_options.IndyFlag) {
345 buf += sprintf(buf, "\\x%.2X", 0xE1); // should output German "sz" in-game.
346 continue;
347 }
348 // fall-through
349 default:
350 e += sprintf(e, "unknown%d(%d)", i, get_word());
351 }
352 } else {
353 if (in_function) {
354 e += sprintf(e, " + ");
355 in_function = false;
356 }
357 if (!in) {
358 *e++ = '"';
359 in = true;
360 }
361 e = put_ascii(e, cmd);
362 }
363 }
364 if (in)
365 *e++ = '"';
366 *e = 0;
367 return e;
368 }
369