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 "ags/lib/std/algorithm.h"
24 #include "ags/engine/ac/string.h"
25 #include "ags/shared/ac/common.h"
26 #include "ags/engine/ac/display.h"
27 #include "ags/shared/ac/game_setup_struct.h"
28 #include "ags/engine/ac/game_state.h"
29 #include "ags/engine/ac/global_translation.h"
30 #include "ags/engine/ac/runtime_defines.h"
31 #include "ags/engine/ac/dynobj/script_string.h"
32 #include "ags/shared/font/fonts.h"
33 #include "ags/engine/debugging/debug_log.h"
34 #include "ags/engine/script/runtime_script_value.h"
35 #include "ags/shared/util/string_compat.h"
36 #include "ags/shared/debugging/out.h"
37 #include "ags/engine/script/script_api.h"
38 #include "ags/engine/script/script_runtime.h"
39 #include "ags/engine/ac/math.h"
40 #include "ags/globals.h"
41
42 namespace AGS3 {
43
String_IsNullOrEmpty(const char * thisString)44 int String_IsNullOrEmpty(const char *thisString) {
45 if ((thisString == nullptr) || (thisString[0] == 0))
46 return 1;
47
48 return 0;
49 }
50
String_Copy(const char * srcString)51 const char *String_Copy(const char *srcString) {
52 return CreateNewScriptString(srcString);
53 }
54
String_Append(const char * thisString,const char * extrabit)55 const char *String_Append(const char *thisString, const char *extrabit) {
56 char *buffer = (char *)malloc(strlen(thisString) + strlen(extrabit) + 1);
57 strcpy(buffer, thisString);
58 strcat(buffer, extrabit);
59 return CreateNewScriptString(buffer, false);
60 }
61
String_AppendChar(const char * thisString,char extraOne)62 const char *String_AppendChar(const char *thisString, char extraOne) {
63 char *buffer = (char *)malloc(strlen(thisString) + 2);
64 sprintf(buffer, "%s%c", thisString, extraOne);
65 return CreateNewScriptString(buffer, false);
66 }
67
String_ReplaceCharAt(const char * thisString,int index,char newChar)68 const char *String_ReplaceCharAt(const char *thisString, int index, char newChar) {
69 size_t len = ustrlen(thisString);
70 if ((index < 0) || ((size_t)index >= len))
71 quit("!String.ReplaceCharAt: index outside range of string");
72
73 size_t off = uoffset(thisString, index);
74 int uchar = ugetc(thisString + off);
75 size_t remain_sz = strlen(thisString + off);
76 size_t old_sz = ucwidth(uchar);
77 size_t new_sz = sizeof(char); // TODO: support unicode char in API
78 size_t total_sz = off + remain_sz + new_sz - old_sz + 1;
79 char *buffer = (char *)malloc(total_sz);
80 memcpy(buffer, thisString, off);
81 usetc(buffer + off, newChar);
82 memcpy(buffer + off + new_sz, thisString + off + old_sz, remain_sz - old_sz + 1);
83 return CreateNewScriptString(buffer, false);
84 }
85
String_Truncate(const char * thisString,int length)86 const char *String_Truncate(const char *thisString, int length) {
87 if (length < 0)
88 quit("!String.Truncate: invalid length");
89 size_t strlen = ustrlen(thisString);
90 if ((size_t)length >= strlen)
91 return thisString;
92
93 size_t sz = uoffset(thisString, length);
94 char *buffer = (char *)malloc(sz + 1);
95 memcpy(buffer, thisString, sz);
96 buffer[sz] = 0;
97 return CreateNewScriptString(buffer, false);
98 }
99
String_Substring(const char * thisString,int index,int length)100 const char *String_Substring(const char *thisString, int index, int length) {
101 if (length < 0)
102 quit("!String.Substring: invalid length");
103 size_t strlen = ustrlen(thisString);
104 if ((index < 0) || ((size_t)index > strlen))
105 quit("!String.Substring: invalid index");
106 size_t sublen = std::min((size_t)length, strlen - index);
107 size_t start = uoffset(thisString, index);
108 size_t end = uoffset(thisString + start, sublen) + start;
109 size_t copysz = end - start;
110
111 char *buffer = (char *)malloc(copysz + 1);
112 memcpy(buffer, thisString + start, copysz);
113 buffer[copysz] = 0;
114 return CreateNewScriptString(buffer, false);
115 }
116
String_CompareTo(const char * thisString,const char * otherString,bool caseSensitive)117 int String_CompareTo(const char *thisString, const char *otherString, bool caseSensitive) {
118
119 if (caseSensitive) {
120 return strcmp(thisString, otherString);
121 } else {
122 return ustricmp(thisString, otherString);
123 }
124 }
125
String_StartsWith(const char * thisString,const char * checkForString,bool caseSensitive)126 int String_StartsWith(const char *thisString, const char *checkForString, bool caseSensitive) {
127
128 if (caseSensitive) {
129 return (strncmp(thisString, checkForString, strlen(checkForString)) == 0) ? 1 : 0;
130 } else {
131 return (ustrnicmp(thisString, checkForString, ustrlen(checkForString)) == 0) ? 1 : 0;
132 }
133 }
134
String_EndsWith(const char * thisString,const char * checkForString,bool caseSensitive)135 int String_EndsWith(const char *thisString, const char *checkForString, bool caseSensitive) {
136 // NOTE: we need size in bytes here
137 size_t thislen = strlen(thisString), checklen = strlen(checkForString);
138 if (checklen > thislen)
139 return 0;
140
141 if (caseSensitive) {
142 return (strcmp(thisString + (thislen - checklen), checkForString) == 0) ? 1 : 0;
143 } else {
144 return (ustricmp(thisString + (thislen - checklen), checkForString) == 0) ? 1 : 0;
145 }
146 }
147
String_Replace(const char * thisString,const char * lookForText,const char * replaceWithText,bool caseSensitive)148 const char *String_Replace(const char *thisString, const char *lookForText, const char *replaceWithText, bool caseSensitive) {
149 char resultBuffer[STD_BUFFER_SIZE] = "";
150 size_t outputSize = 0; // length in bytes
151 if (caseSensitive) {
152 size_t lookForLen = strlen(lookForText);
153 size_t replaceLen = strlen(replaceWithText);
154 for (const char *ptr = thisString; *ptr; ++ptr) {
155 if (strncmp(ptr, lookForText, lookForLen) == 0) {
156 memcpy(&resultBuffer[outputSize], replaceWithText, replaceLen);
157 outputSize += replaceLen;
158 ptr += lookForLen - 1;
159 } else {
160 resultBuffer[outputSize] = *ptr;
161 outputSize++;
162 }
163 }
164 } else {
165 size_t lookForLen = ustrlen(lookForText);
166 size_t lookForSz = strlen(lookForText); // length in bytes
167 size_t replaceSz = strlen(replaceWithText); // length in bytes
168 const char *p_cur = thisString;
169 for (int c = ugetxc(&thisString); *p_cur; p_cur = thisString, c = ugetxc(&thisString)) {
170 if (ustrnicmp(p_cur, lookForText, lookForLen) == 0) {
171 memcpy(&resultBuffer[outputSize], replaceWithText, replaceSz);
172 outputSize += replaceSz;
173 thisString = p_cur + lookForSz;
174 } else {
175 usetc(&resultBuffer[outputSize], c);
176 outputSize += ucwidth(c);
177 }
178 }
179 }
180
181 resultBuffer[outputSize] = 0; // terminate
182 return CreateNewScriptString(resultBuffer, true);
183 }
184
String_LowerCase(const char * thisString)185 const char *String_LowerCase(const char *thisString) {
186 char *buffer = ags_strdup(thisString);
187 ustrlwr(buffer);
188 return CreateNewScriptString(buffer, false);
189 }
190
String_UpperCase(const char * thisString)191 const char *String_UpperCase(const char *thisString) {
192 char *buffer = ags_strdup(thisString);
193 ustrupr(buffer);
194 return CreateNewScriptString(buffer, false);
195 }
196
String_GetChars(const char * texx,int index)197 int String_GetChars(const char *texx, int index) {
198 if ((index < 0) || (index >= (int)strlen(texx)))
199 return 0;
200 return texx[index];
201 }
202
StringToInt(const char * stino)203 int StringToInt(const char *stino) {
204 return atoi(stino);
205 }
206
StrContains(const char * s1,const char * s2)207 int StrContains(const char *s1, const char *s2) {
208 VALIDATE_STRING(s1);
209 VALIDATE_STRING(s2);
210 char *tempbuf1 = ags_strdup(s1);
211 char *tempbuf2 = ags_strdup(s2);
212 ustrlwr(tempbuf1);
213 ustrlwr(tempbuf2);
214
215 char *offs = const_cast<char *>(ustrstr(tempbuf1, tempbuf2));
216
217 if (offs == nullptr) {
218 free(tempbuf1);
219 free(tempbuf2);
220 return -1;
221 }
222
223 *offs = 0;
224 int at = ustrlen(tempbuf1);
225 free(tempbuf1);
226 free(tempbuf2);
227 return at;
228 }
229
230 //=============================================================================
231
CreateNewScriptString(const String & fromText)232 const char *CreateNewScriptString(const String &fromText) {
233 return (const char *)CreateNewScriptStringObj(fromText.GetCStr(), true).second;
234 }
235
CreateNewScriptString(const char * fromText,bool reAllocate)236 const char *CreateNewScriptString(const char *fromText, bool reAllocate) {
237 return (const char *)CreateNewScriptStringObj(fromText, reAllocate).second;
238 }
239
CreateNewScriptStringObj(const String & fromText)240 DynObjectRef CreateNewScriptStringObj(const String &fromText) {
241 return CreateNewScriptStringObj(fromText.GetCStr(), true);
242 }
243
CreateNewScriptStringObj(const char * fromText,bool reAllocate)244 DynObjectRef CreateNewScriptStringObj(const char *fromText, bool reAllocate) {
245 ScriptString *str;
246 if (reAllocate) {
247 str = new ScriptString(fromText);
248 } else {
249 str = new ScriptString();
250 str->text = const_cast<char *>(fromText);
251 }
252 void *obj_ptr = str->text;
253 int32_t handle = ccRegisterManagedObject(obj_ptr, str);
254 if (handle == 0) {
255 delete str;
256 return DynObjectRef(0, nullptr);
257 }
258 return DynObjectRef(handle, obj_ptr);
259 }
260
break_up_text_into_lines(const char * todis,SplitLines & lines,int wii,int fonnt,size_t max_lines)261 size_t break_up_text_into_lines(const char *todis, SplitLines &lines, int wii, int fonnt, size_t max_lines) {
262 if (fonnt == -1)
263 fonnt = _GP(play).normal_font;
264
265 // char sofar[100];
266 if (todis[0] == '&') {
267 while ((todis[0] != ' ') & (todis[0] != 0)) todis++;
268 if (todis[0] == ' ') todis++;
269 }
270 lines.Reset();
271 _G(longestline) = 0;
272
273 // Don't attempt to display anything if the width is tiny
274 if (wii < 3)
275 return 0;
276
277 int line_length;
278
279 split_lines(todis, lines, wii, fonnt, max_lines);
280
281 // Right-to-left just means reverse the text then
282 // write it as normal
283 if (_GP(game).options[OPT_RIGHTLEFTWRITE])
284 for (size_t rr = 0; rr < lines.Count(); rr++) {
285 (get_uformat() == U_UTF8) ?
286 lines[rr].ReverseUTF8() :
287 lines[rr].Reverse();
288 line_length = wgettextwidth_compensate(lines[rr].GetCStr(), fonnt);
289 if (line_length > _G(longestline))
290 _G(longestline) = line_length;
291 } else
292 for (size_t rr = 0; rr < lines.Count(); rr++) {
293 line_length = wgettextwidth_compensate(lines[rr].GetCStr(), fonnt);
294 if (line_length > _G(longestline))
295 _G(longestline) = line_length;
296 }
297 return lines.Count();
298 }
299
300 int MAXSTRLEN = MAX_MAXSTRLEN;
check_strlen(char * ptt)301 void check_strlen(char *ptt) {
302 MAXSTRLEN = MAX_MAXSTRLEN;
303 long charstart = (intptr_t)&_GP(game).chars[0];
304 long charend = charstart + sizeof(CharacterInfo) * _GP(game).numcharacters;
305 if (((intptr_t)&ptt[0] >= charstart) && ((intptr_t)&ptt[0] <= charend))
306 MAXSTRLEN = 30;
307 }
308
309 /*void GetLanguageString(int indxx,char*buffr) {
310 VALIDATE_STRING(buffr);
311 char*bptr=get_language_text(indxx);
312 if (bptr==NULL) strcpy(buffr,"[language string error]");
313 else strncpy(buffr,bptr,199);
314 buffr[199]=0;
315 }*/
316
my_strncpy(char * dest,const char * src,int len)317 void my_strncpy(char *dest, const char *src, int len) {
318 // the normal strncpy pads out the string with zeros up to the
319 // max length -- we don't want that
320 if (strlen(src) >= (unsigned)len) {
321 strncpy(dest, src, len);
322 dest[len] = 0;
323 } else
324 strcpy(dest, src);
325 }
326
327 //=============================================================================
328 //
329 // Script API Functions
330 //
331 //=============================================================================
332
333 // int (const char *thisString)
Sc_String_IsNullOrEmpty(const RuntimeScriptValue * params,int32_t param_count)334 RuntimeScriptValue Sc_String_IsNullOrEmpty(const RuntimeScriptValue *params, int32_t param_count) {
335 API_SCALL_INT_POBJ(String_IsNullOrEmpty, const char);
336 }
337
338 // const char* (const char *thisString, const char *extrabit)
Sc_String_Append(void * self,const RuntimeScriptValue * params,int32_t param_count)339 RuntimeScriptValue Sc_String_Append(void *self, const RuntimeScriptValue *params, int32_t param_count) {
340 API_CONST_OBJCALL_OBJ_POBJ(const char, const char, _GP(myScriptStringImpl), String_Append, const char);
341 }
342
343 // const char* (const char *thisString, char extraOne)
Sc_String_AppendChar(void * self,const RuntimeScriptValue * params,int32_t param_count)344 RuntimeScriptValue Sc_String_AppendChar(void *self, const RuntimeScriptValue *params, int32_t param_count) {
345 API_CONST_OBJCALL_OBJ_PINT(const char, const char, _GP(myScriptStringImpl), String_AppendChar);
346 }
347
348 // int (const char *thisString, const char *otherString, bool caseSensitive)
Sc_String_CompareTo(void * self,const RuntimeScriptValue * params,int32_t param_count)349 RuntimeScriptValue Sc_String_CompareTo(void *self, const RuntimeScriptValue *params, int32_t param_count) {
350 API_OBJCALL_INT_POBJ_PBOOL(const char, String_CompareTo, const char);
351 }
352
353 // int (const char *s1, const char *s2)
Sc_StrContains(void * self,const RuntimeScriptValue * params,int32_t param_count)354 RuntimeScriptValue Sc_StrContains(void *self, const RuntimeScriptValue *params, int32_t param_count) {
355 API_OBJCALL_INT_POBJ(const char, StrContains, const char);
356 }
357
358 // const char* (const char *srcString)
Sc_String_Copy(void * self,const RuntimeScriptValue * params,int32_t param_count)359 RuntimeScriptValue Sc_String_Copy(void *self, const RuntimeScriptValue *params, int32_t param_count) {
360 API_CONST_OBJCALL_OBJ(const char, const char, _GP(myScriptStringImpl), String_Copy);
361 }
362
363 // int (const char *thisString, const char *checkForString, bool caseSensitive)
Sc_String_EndsWith(void * self,const RuntimeScriptValue * params,int32_t param_count)364 RuntimeScriptValue Sc_String_EndsWith(void *self, const RuntimeScriptValue *params, int32_t param_count) {
365 API_OBJCALL_INT_POBJ_PBOOL(const char, String_EndsWith, const char);
366 }
367
368 // const char* (const char *texx, ...)
Sc_String_Format(const RuntimeScriptValue * params,int32_t param_count)369 RuntimeScriptValue Sc_String_Format(const RuntimeScriptValue *params, int32_t param_count) {
370 API_SCALL_SCRIPT_SPRINTF(String_Format, 1);
371 return RuntimeScriptValue().SetDynamicObject(const_cast<char *>(CreateNewScriptString(scsf_buffer)), &_GP(myScriptStringImpl));
372 }
373
374 // const char* (const char *thisString)
Sc_String_LowerCase(void * self,const RuntimeScriptValue * params,int32_t param_count)375 RuntimeScriptValue Sc_String_LowerCase(void *self, const RuntimeScriptValue *params, int32_t param_count) {
376 API_CONST_OBJCALL_OBJ(const char, const char, _GP(myScriptStringImpl), String_LowerCase);
377 }
378
379 // const char* (const char *thisString, const char *lookForText, const char *replaceWithText, bool caseSensitive)
Sc_String_Replace(void * self,const RuntimeScriptValue * params,int32_t param_count)380 RuntimeScriptValue Sc_String_Replace(void *self, const RuntimeScriptValue *params, int32_t param_count) {
381 API_CONST_OBJCALL_OBJ_POBJ2_PBOOL(const char, const char, _GP(myScriptStringImpl), String_Replace, const char, const char);
382 }
383
384 // const char* (const char *thisString, int index, char newChar)
Sc_String_ReplaceCharAt(void * self,const RuntimeScriptValue * params,int32_t param_count)385 RuntimeScriptValue Sc_String_ReplaceCharAt(void *self, const RuntimeScriptValue *params, int32_t param_count) {
386 API_CONST_OBJCALL_OBJ_PINT2(const char, const char, _GP(myScriptStringImpl), String_ReplaceCharAt);
387 }
388
389 // int (const char *thisString, const char *checkForString, bool caseSensitive)
Sc_String_StartsWith(void * self,const RuntimeScriptValue * params,int32_t param_count)390 RuntimeScriptValue Sc_String_StartsWith(void *self, const RuntimeScriptValue *params, int32_t param_count) {
391 API_OBJCALL_INT_POBJ_PBOOL(const char, String_StartsWith, const char);
392 }
393
394 // const char* (const char *thisString, int index, int length)
Sc_String_Substring(void * self,const RuntimeScriptValue * params,int32_t param_count)395 RuntimeScriptValue Sc_String_Substring(void *self, const RuntimeScriptValue *params, int32_t param_count) {
396 API_CONST_OBJCALL_OBJ_PINT2(const char, const char, _GP(myScriptStringImpl), String_Substring);
397 }
398
399 // const char* (const char *thisString, int length)
Sc_String_Truncate(void * self,const RuntimeScriptValue * params,int32_t param_count)400 RuntimeScriptValue Sc_String_Truncate(void *self, const RuntimeScriptValue *params, int32_t param_count) {
401 API_CONST_OBJCALL_OBJ_PINT(const char, const char, _GP(myScriptStringImpl), String_Truncate);
402 }
403
404 // const char* (const char *thisString)
Sc_String_UpperCase(void * self,const RuntimeScriptValue * params,int32_t param_count)405 RuntimeScriptValue Sc_String_UpperCase(void *self, const RuntimeScriptValue *params, int32_t param_count) {
406 API_CONST_OBJCALL_OBJ(const char, const char, _GP(myScriptStringImpl), String_UpperCase);
407 }
408
409 // FLOAT_RETURN_TYPE (const char *theString);
Sc_StringToFloat(void * self,const RuntimeScriptValue * params,int32_t param_count)410 RuntimeScriptValue Sc_StringToFloat(void *self, const RuntimeScriptValue *params, int32_t param_count) {
411 API_OBJCALL_FLOAT(const char, StringToFloat);
412 }
413
414 // int (char*stino)
Sc_StringToInt(void * self,const RuntimeScriptValue * params,int32_t param_count)415 RuntimeScriptValue Sc_StringToInt(void *self, const RuntimeScriptValue *params, int32_t param_count) {
416 API_OBJCALL_INT(const char, StringToInt);
417 }
418
419 // int (const char *texx, int index)
Sc_String_GetChars(void * self,const RuntimeScriptValue * params,int32_t param_count)420 RuntimeScriptValue Sc_String_GetChars(void *self, const RuntimeScriptValue *params, int32_t param_count) {
421 API_OBJCALL_INT_PINT(const char, String_GetChars);
422 }
423
Sc_strlen(void * self,const RuntimeScriptValue * params,int32_t param_count)424 RuntimeScriptValue Sc_strlen(void *self, const RuntimeScriptValue *params, int32_t param_count) {
425 ASSERT_SELF(strlen);
426 return RuntimeScriptValue().SetInt32(strlen((const char *)self));
427 }
428
429 //=============================================================================
430 //
431 // Exclusive API for Plugins
432 //
433 //=============================================================================
434
RegisterStringAPI()435 void RegisterStringAPI() {
436 ccAddExternalStaticFunction("String::IsNullOrEmpty^1", Sc_String_IsNullOrEmpty);
437 ccAddExternalObjectFunction("String::Append^1", Sc_String_Append);
438 ccAddExternalObjectFunction("String::AppendChar^1", Sc_String_AppendChar);
439 ccAddExternalObjectFunction("String::CompareTo^2", Sc_String_CompareTo);
440 ccAddExternalObjectFunction("String::Contains^1", Sc_StrContains);
441 ccAddExternalObjectFunction("String::Copy^0", Sc_String_Copy);
442 ccAddExternalObjectFunction("String::EndsWith^2", Sc_String_EndsWith);
443 ccAddExternalStaticFunction("String::Format^101", Sc_String_Format);
444 ccAddExternalObjectFunction("String::IndexOf^1", Sc_StrContains);
445 ccAddExternalObjectFunction("String::LowerCase^0", Sc_String_LowerCase);
446 ccAddExternalObjectFunction("String::Replace^3", Sc_String_Replace);
447 ccAddExternalObjectFunction("String::ReplaceCharAt^2", Sc_String_ReplaceCharAt);
448 ccAddExternalObjectFunction("String::StartsWith^2", Sc_String_StartsWith);
449 ccAddExternalObjectFunction("String::Substring^2", Sc_String_Substring);
450 ccAddExternalObjectFunction("String::Truncate^1", Sc_String_Truncate);
451 ccAddExternalObjectFunction("String::UpperCase^0", Sc_String_UpperCase);
452 ccAddExternalObjectFunction("String::get_AsFloat", Sc_StringToFloat);
453 ccAddExternalObjectFunction("String::get_AsInt", Sc_StringToInt);
454 ccAddExternalObjectFunction("String::geti_Chars", Sc_String_GetChars);
455 ccAddExternalObjectFunction("String::get_Length", Sc_strlen);
456 }
457
458 } // namespace AGS3
459