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