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 | | * $URL$ |
22 | | * $Id$ |
23 | | * |
24 | | */ |
25 | | |
26 | | #include "sci/engine/state.h" |
27 | | #include "sci/engine/kernel.h" |
28 | | |
29 | | namespace Sci { |
30 | | |
31 | | /* |
32 | | The following is an email Lars wrote mid march and which explains a little |
33 | | bit about system strings. I paste it here for future reference. Some of this |
34 | | could possible be turned into documentation for the strings frags code... |
35 | | */ |
36 | | |
37 | | /* |
38 | | Max Horn wrote: |
39 | | > Basically, saving the reg_t "array" (?) in the SystemString struct (see |
40 | | > sync_SystemString in engine/savegame.cpp) seems like a bad hack that will |
41 | | > result in breakage... |
42 | | |
43 | | The short explanation is that the casts _will_ go away, but that the |
44 | | system strings, too, will become stringfrag-based eventually. |
45 | | |
46 | | The long version requires a bit of background: During the SCI01 |
47 | | timeframe, a Memory() call was added which does not really play well |
48 | | with the Glutton architecture (as well as having the potential for |
49 | | nasty game bugs - it allows games to write to any part of the |
50 | | heap). The same memory call is used for modification of strings and |
51 | | integer arrays (as is direct array indexing). From a formal point of |
52 | | view, the use of either of these for string handling is wrong. There |
53 | | exist string APIs that should be used instead. But things being as |
54 | | they are, we need to be able to tell the two types apart |
55 | | somehow. Currently, we have a heuristic in there which doesn't always |
56 | | work (it breaks LSL5 password protection, for one thing) - the string |
57 | | frags code is intended to replace this heuristic, but requires |
58 | | changing the argument parsing of every kernel function that uses |
59 | | strings. This _will_ cause regressions, and for that reason I only |
60 | | changed the interface definitions, not the implementation. As I wrote |
61 | | to Willem the other day, I was trying to commit as much of the |
62 | | stringfrag code as I could without breaking existing code, and isolate |
63 | | the breakage to a later set of commits. |
64 | | |
65 | | Also, a little background on the system strings. In Sierra SCI, the |
66 | | heap is stored in the interpreter's data segment and is not precisely |
67 | | 64K in size - but rather 64K minus the size of the interpreter's |
68 | | static data. However, the script code does have read/write access |
69 | | to this static data. The strings that we store in the system strings |
70 | | table are stored in static data in Sierra SCI. We have only two at |
71 | | this point: |
72 | | |
73 | | 1. The savegame directory |
74 | | 2. Parser error handling |
75 | | |
76 | | 2) doesn't actually need to be saved, because not only is the variable |
77 | | only used during error handling, but the routine in question is called |
78 | | re-entrantly by the kernel function - and not even the old save system |
79 | | supports saving in that kind of situation, much less Sierra SCI. On |
80 | | the other hand, the scripts retain a pointer to 1) which needs to |
81 | | remain valid across saves. |
82 | | */ |
83 | | |
84 | | #define STRINGFRAG_SEGMENT s->string_frag_segment |
85 | | // #define STRINGFRAG_SEGMENT 0xffff |
86 | | |
87 | | static int internal_is_valid_stringfrag(EngineState *s, reg_t *buffer) { |
88 | | if (buffer == NULL) |
89 | | return 0; |
90 | | |
91 | | while ((buffer->offset & 0xff00) != 0 && |
92 | | (buffer->offset & 0x00ff) != 0) { |
93 | | if (buffer->segment != STRINGFRAG_SEGMENT) |
94 | | return 0; |
95 | | buffer++; |
96 | | } |
97 | | |
98 | | if (buffer->segment != STRINGFRAG_SEGMENT) { |
99 | | return 0; |
100 | | } |
101 | | |
102 | | return 1; |
103 | | } |
104 | | |
105 | | int is_valid_stringfrag(EngineState *s, reg_t pos) { |
106 | | reg_t *buffer = s->segMan->derefRegPtr(pos, 1); |
107 | | |
108 | | return internal_is_valid_stringfrag(s, buffer); |
109 | | } |
110 | | |
111 | | static int internal_stringfrag_length(EngineState *s, reg_t *buffer) { |
112 | | int result = 0; |
113 | | |
114 | | if (buffer == NULL) { |
115 | | // error("Error: Invalid stringfrag handle"); |
116 | | return 0; |
117 | | } |
118 | | |
119 | | while ((buffer->offset & 0xff00) != 0 && |
120 | | (buffer->offset & 0x00ff) != 0) { |
121 | | if (buffer->segment != STRINGFRAG_SEGMENT) { |
122 | | // error("Error: Invalid stringfrag handle"); |
123 | | return 0; |
124 | | } |
125 | | |
126 | | result += 2; |
127 | | buffer++; |
128 | | } |
129 | | |
130 | | if (buffer->segment != STRINGFRAG_SEGMENT) { |
131 | | error("Error: Invalid stringfrag handle"); |
132 | | return 0; |
133 | | } |
134 | | |
135 | | if ((buffer->offset & 0xff00) != 0) |
136 | | result++; |
137 | | return result; |
138 | | } |
139 | | |
140 | | int stringfrag_length(EngineState *s, reg_t pos) { |
141 | | reg_t *buffer = s->segMan->derefRegPtr(pos, 1); |
142 | | |
143 | | return internal_stringfrag_length(s, buffer); |
144 | | } |
145 | | |
146 | | static void internal_stringfrag_to_ascii(EngineState *s, reg_t *buffer) { |
147 | | int str_length = internal_stringfrag_length(s, buffer); |
148 | | char *temp = (char *)malloc(str_length + 1); |
149 | | int i = 0; |
150 | | |
151 | | while ((buffer->offset & 0xff00) != 0 && |
152 | | (buffer->offset & 0x00ff) != 0) { |
153 | | temp[i] = (buffer->offset & 0xff00) >> 8; |
154 | | if (temp[i] != 0) |
155 | | temp[i+1] = buffer->offset & 0xff; |
156 | | i += 2; |
157 | | buffer++; |
158 | | } |
159 | | |
160 | | if ((buffer->offset & 0xff00) != 0) { |
161 | | temp[i] = (buffer->offset & 0xff00) >> 8; |
162 | | temp[i+1] = 0; |
163 | | } else { |
164 | | temp[i] = 0; |
165 | | } |
166 | | |
167 | | strcpy((char *)buffer, temp); |
168 | | free(temp); |
169 | | } |
170 | | |
171 | | void stringfrag_to_ascii(EngineState *s, reg_t pos) { |
172 | | reg_t *buffer = s->segMan->derefRegPtr(pos, 1); |
173 | | |
174 | | internal_stringfrag_to_ascii(s, buffer); |
175 | | } |
176 | | |
177 | | static void internal_ascii_to_stringfrag(EngineState *s, reg_t *buffer) { |
178 | | int str_length = strlen((char *)buffer); |
179 | | int words = str_length % 2 ? (str_length+2)/2 : (str_length + 1)/2; |
180 | | char *temp = (char *)malloc(str_length + 1); |
181 | | |
182 | | strcpy(temp, (char *)buffer); |
183 | | for (int i = 0; i < words; i++) { |
184 | | buffer[i].segment = STRINGFRAG_SEGMENT; |
185 | | buffer[i].offset = temp[i*2]; |
186 | | if (temp[i*2]) |
187 | | buffer[i].offset |= temp[i*2+1]; |
188 | | } |
189 | | |
190 | | free(temp); |
191 | | } |
192 | | |
193 | | void ascii_to_stringfrag(EngineState *s, reg_t pos) { |
194 | | reg_t *buffer = s->segMan->derefRegPtr(pos, 1); |
195 | | |
196 | | internal_ascii_to_stringfrag(s, buffer); |
197 | | } |
198 | | |
199 | | static void internal_stringfrag_append_char(EngineState *s, reg_t *buffer, unsigned char c) { |
200 | | while ((buffer->offset & 0xff00) != 0 && |
201 | | (buffer->offset & 0x00ff) != 0) |
202 | | buffer++; |
203 | | |
204 | | if ((buffer->offset & 0xff00) == 0) { |
205 | | buffer->offset = c << 8; |
206 | | } else { |
207 | | if ((buffer->offset & 0x00ff) == 0) { |
208 | | buffer->offset |= c; |
209 | | buffer++; |
210 | | buffer->segment = STRINGFRAG_SEGMENT; |
211 | | buffer->offset = 0; |
212 | | } |
213 | | } |
214 | | } |
215 | | |
216 | | void stringfrag_append_char(EngineState *s, reg_t pos, unsigned char c) { |
217 | | reg_t *buffer = s->segMan->derefRegPtr(pos, 1); |
218 | | |
219 | | internal_stringfrag_append_char(s, buffer, c); |
220 | | } |
221 | | |
222 | | void stringfrag_setchar(reg_t *buffer, int pos, int offset, unsigned char c) { |
223 | | switch (offset) { |
224 | | case 0 : |
225 | | buffer[pos].offset = (buffer[pos].offset & 0x00ff) | (c << 8); |
226 | | break; |
227 | | case 1 : |
228 | | buffer[pos].offset = (buffer[pos].offset & 0xff00) | c; |
229 | | break; |
230 | | } |
231 | | } |
232 | | |
233 | | unsigned char stringfrag_getchar(reg_t *buffer, int pos, int offset) { |
234 | | switch (offset) { |
235 | | case 0 : |
236 | | return buffer[pos].offset >> 8; |
237 | | case 1 : |
238 | | return buffer[pos].offset & 0xff; |
239 | | default: |
240 | | return 0; // FIXME: Is it safe to return "0" here? |
241 | | } |
242 | | } |
243 | | |
244 | | void stringfrag_memmove(EngineState *s, reg_t *buffer, int sourcepos, int destpos, int count) { |
245 | | /* Some of these values are not used yet. There are a couple |
246 | | of cases that we could implement faster if the need arises, in |
247 | | which case we would most certainly need these. */ |
248 | | int source_begin_pos = sourcepos/2; |
249 | | int source_begin_offset = sourcepos%2; |
250 | | int source_end_pos = (sourcepos+count-1)/2; |
251 | | int source_end_offset = (sourcepos+count-1)%2; |
252 | | |
253 | | int dest_begin_pos = destpos/2; |
254 | | int dest_begin_offset = destpos%2; |
255 | | int dest_end_pos = (destpos+count-1)/2; |
256 | | int dest_end_offset = (destpos+count-1)%2; |
257 | | |
258 | | if (sourcepos < destpos) { |
259 | | for (int n = count-1; n >= 0; n--) { |
260 | | buffer[dest_end_pos].segment = STRINGFRAG_SEGMENT; |
261 | | stringfrag_setchar(buffer, dest_end_pos, dest_end_offset, |
262 | | stringfrag_getchar(buffer, source_end_pos, source_end_offset)); |
263 | | if (source_end_offset ^= 1) |
264 | | source_end_pos --; |
265 | | if (dest_end_offset ^= 1) |
266 | | dest_end_pos --; |
267 | | } |
268 | | } else { |
269 | | for (int n = 0; n < count; n++) { |
270 | | buffer[dest_begin_pos].segment = STRINGFRAG_SEGMENT; |
271 | | stringfrag_setchar(buffer, dest_begin_pos, dest_begin_offset, |
272 | | stringfrag_getchar(buffer, source_begin_pos, source_begin_offset)); |
273 | | if (!(source_begin_offset ^= 1)) |
274 | | source_begin_pos ++; |
275 | | if (!(dest_begin_offset ^= 1)) |
276 | | dest_begin_pos ++; |
277 | | } |
278 | | } |
279 | | } |
280 | | |
281 | | static void internal_stringfrag_insert_char(EngineState *s, reg_t *buffer, int p, unsigned char c) { |
282 | | reg_t *save = buffer + p/2; |
283 | | reg_t *seeker = buffer + p/2; |
284 | | int restore_nul_offset; |
285 | | int restore_nul_pos; |
286 | | int len = internal_stringfrag_length(s, buffer); |
287 | | |
288 | | while ((seeker->offset & 0xff00) != 0 && |
289 | | (seeker->offset & 0x00ff) != 0) |
290 | | seeker++; |
291 | | |
292 | | /* The variables restore_null_offset and restore_null_pos will |
293 | | indicate where the NUL character should be PUT BACK after |
294 | | inserting c, as this operation might overwrite the NUL. */ |
295 | | if (seeker->offset & 0xff00) { |
296 | | restore_nul_offset = 1; |
297 | | restore_nul_pos = 0; |
298 | | } else { |
299 | | restore_nul_offset = 0; |
300 | | restore_nul_pos = 1; |
301 | | } |
302 | | |
303 | | if (seeker-save == 0) { // So p is at the end, use fast method |
304 | | internal_stringfrag_append_char(s, seeker, c); |
305 | | return; |
306 | | } |
307 | | |
308 | | stringfrag_memmove(s, buffer, p, p+1, len-p); |
309 | | stringfrag_setchar(buffer, p/2, p%2, c); |
310 | | stringfrag_setchar(seeker, restore_nul_pos, restore_nul_offset, 0); |
311 | | } |
312 | | |
313 | | void stringfrag_insert_char(EngineState *s, reg_t pos, int p, unsigned char c) { |
314 | | reg_t *buffer = s->segMan->derefRegPtr(pos, 1); |
315 | | |
316 | | internal_stringfrag_insert_char(s, buffer, p, c); |
317 | | } |
318 | | |
319 | | static void internal_stringfrag_delete_char(EngineState *s, reg_t *buffer, int p) { |
320 | | //reg_t *save = buffer + p; |
321 | | reg_t *seeker = buffer + p; |
322 | | int restore_nul_offset; |
323 | | int restore_nul_pos; |
324 | | int len = internal_stringfrag_length(s, buffer); |
325 | | |
326 | | while ((seeker->offset & 0xff00) != 0 && |
327 | | (seeker->offset & 0x00ff) != 0) |
328 | | seeker++; |
329 | | |
330 | | /* The variables restore_null_offset and restore_null_pos will |
331 | | indicate where the NUL character should be PUT BACK after |
332 | | deletion, as this operation might overwrite the NUL. */ |
333 | | if (seeker->offset & 0xff00) { |
334 | | restore_nul_offset = 1; |
335 | | restore_nul_pos = -1; |
336 | | } else { |
337 | | restore_nul_offset = 0; |
338 | | restore_nul_pos = 0; |
339 | | } |
340 | | |
341 | | stringfrag_memmove(s, buffer, p, p-1, len-p); |
342 | | stringfrag_setchar(seeker, restore_nul_pos, restore_nul_offset, 0); |
343 | | } |
344 | | |
345 | | void stringfrag_delete_char(EngineState *s, reg_t pos, int p) { |
346 | | reg_t *buffer = s->segMan->derefRegPtr(pos, 1); |
347 | | |
348 | | internal_stringfrag_delete_char(s, buffer, p); |
349 | | } |
350 | | |
351 | | void internal_stringfrag_strcpy(EngineState *s, reg_t *dest, reg_t *src) { |
352 | | while ((src->offset & 0xff00) != 0 && |
353 | | (src->offset & 0x00ff) != 0) { |
354 | | *dest = *src; |
355 | | dest++; |
356 | | src++; |
357 | | } |
358 | | |
359 | | *dest = *src; |
360 | | } |
361 | | |
362 | | void stringfrag_strcpy(EngineState *s, reg_t dest, reg_t src) { |
363 | | reg_t *destbuf = s->segMan->derefRegPtr(dest, 1); |
364 | | reg_t *srcbuf = s->segMan->derefRegPtr(src, 1); |
365 | | |
366 | | internal_stringfrag_strcpy(s, destbuf, srcbuf); |
367 | | } |
368 | | |
369 | | void internal_stringfrag_strncpy(EngineState *s, reg_t *dest, reg_t *src, int len) { |
370 | | while ((src->offset & 0xff00) != 0 && |
371 | | (src->offset & 0x00ff) != 0 && |
372 | | (len -= 2) > 1) { |
373 | | *dest = *src; |
374 | | dest++; |
375 | | src++; |
376 | | } |
377 | | |
378 | | for (; len > 1; len -= 2) { |
379 | | dest->segment = STRINGFRAG_SEGMENT; |
380 | | dest->offset = 0; |
381 | | len -= 2; |
382 | | } |
383 | | |
384 | | if (len == 1) |
385 | | stringfrag_setchar(dest, 0, 0, 0); |
386 | | |
387 | | *dest = *src; |
388 | | } |
389 | | |
390 | | void stringfrag_strncpy(EngineState *s, reg_t dest, reg_t src, int len) { |
391 | | reg_t *destbuf = s->segMan->derefRegPtr(dest, 1); |
392 | | reg_t *srcbuf = s->segMan->derefRegPtr(src, 1); |
393 | | |
394 | | internal_stringfrag_strncpy(s, destbuf, srcbuf, len); |
395 | | } |
396 | | |
397 | | int internal_stringfrag_strcmp(EngineState *s, reg_t *s1, reg_t *s2) { |
398 | | int c1, c2; |
399 | | while (1) { |
400 | | c1 = (uint16)(s1->offset & 0xff00); |
401 | | c2 = (uint16)(s2->offset & 0xff00); |
402 | | if (c1 != c2) // We found a difference |
403 | | return c1 - c2; |
404 | | else if (c1 == 0) // Both strings ended |
405 | | return 0; |
406 | | |
407 | | c1 = (uint16)(s1->offset & 0x00ff); |
408 | | c2 = (uint16)(s2->offset & 0x00ff); |
409 | | if (c1 != c2) // We found a difference |
410 | | return c1 - c2; |
411 | | else if (c1 == 0) // Both strings ended |
412 | | return 0; |
413 | | } |
414 | | |
415 | | return 0; |
416 | | } |
417 | | |
418 | | void stringfrag_strcmp(EngineState *s, reg_t s1, reg_t s2) { |
419 | | reg_t *s1buf = s->segMan->derefRegPtr(s1, 1); |
420 | | reg_t *s2buf = s->segMan->derefRegPtr(s2, 1); |
421 | | |
422 | | internal_stringfrag_strcmp(s, s1buf, s2buf); |
423 | | } |
424 | | |
425 | | int internal_stringfrag_strncmp(EngineState *s, reg_t *s1, reg_t *s2, int len) { |
426 | | int c1, c2; |
427 | | while (len) { |
428 | | if (len--) |
429 | | return 0; |
430 | | c1 = (uint16)(s1->offset & 0xff00); |
431 | | c2 = (uint16)(s2->offset & 0xff00); |
432 | | if (c1 != c2) // We found a difference |
433 | | return c1 - c2; |
434 | | else if (c1 == 0) // Both strings ended |
435 | | return 0; |
436 | | |
437 | | if (len--) |
438 | | return 0; |
439 | | |
440 | | c1 = (uint16)(s1->offset & 0x00ff); |
441 | | c2 = (uint16)(s2->offset & 0x00ff); |
442 | | if (c1 != c2) // We found a difference |
443 | | return c1 - c2; |
444 | | else if (c1 == 0) // Both strings ended |
445 | | return 0; |
446 | | } |
447 | | |
448 | | return 0; |
449 | | } |
450 | | |
451 | | void stringfrag_strncmp(EngineState *s, reg_t s1, reg_t s2, int len) { |
452 | | reg_t *s1buf = s->segMan->derefRegPtr(s1, 1); |
453 | | reg_t *s2buf = s->segMan->derefRegPtr(s2, 1); |
454 | | |
455 | | internal_stringfrag_strncmp(s, s1buf, s2buf, len); |
456 | | } |
457 | | |
458 | | } // end of namespace Sci |