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