[Scummvm-git-logs] scummvm master -> c34807897ee43161db412b2413d6362799658c0f

dreammaster dreammaster at scummvm.org
Sat Jul 10 21:35:40 UTC 2021


This automated email contains information about 20 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
f1711431f6 AGS: Unicode-aware line splitting and right-to-left text mode
e3a9f13ae0 AGS: Added needed Allegro unicode methods
656659d488 AGS: Unicode-aware script String API implementation
73cd6b9215 AGS: Structure for passing events UTF8 text around along with the keycode
4cb95e324e AGS: TextBox control supports unicode
5dec9be2e9 AGS: Skeleton for converting UTF-8 translation keys to ASCII
adc208ca4f AGS: text parser's F3 command works with unicode
188951a499 AGS: Supports reading scripts as separate assets
02883d6d89 AGS: Supports reading room scripts as separate assets
ba10eb247e AGS: print original filename for import errors if possible
7a61c3838a AGS: Added GetScriptAPIName() and use it instead of an array
4271f74caf AGS: Added API level for v3.6.0
1b0cbbaafc AGS: implemented System.Log()
8fc03ec83b AGS: Fixed DebugManager::RegisterGroup
04d6a41c42 AGS: Simplified System.Log implementation
a8f4d9dc5b AGS: Safety check for asset library v20, for asset name over limit
7d01f1dc1f AGS: in asset library files treat offsets & sizes as unsigned values
17b7626d11 AGS: don't mark viewport & camera as "changed" when not necessary
4c5d1335a7 AGS: fixed software mode's dirty rects in the legacy letterbox mode
c34807897e AGS: separate dirty rects function for the engine overlay sprites


Commit: f1711431f6452ead3bbeade0bd24c8eb7bad47f9
    https://github.com/scummvm/scummvm/commit/f1711431f6452ead3bbeade0bd24c8eb7bad47f9
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:35-07:00

Commit Message:
AGS: Unicode-aware line splitting and right-to-left text mode

>From upstream 00fee7e61e5746fe3c8722028f4f497b1949559e

Changed paths:
    engines/ags/engine/ac/string.cpp
    engines/ags/globals.h
    engines/ags/lib/allegro/unicode.cpp
    engines/ags/lib/allegro/unicode.h
    engines/ags/shared/font/fonts.cpp
    engines/ags/shared/util/string.cpp
    engines/ags/shared/util/string.h


diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index fc2ce987a8..a820b80bb7 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -252,7 +252,10 @@ size_t break_up_text_into_lines(const char *todis, SplitLines &lines, int wii, i
 	// write it as normal
 	if (_GP(game).options[OPT_RIGHTLEFTWRITE])
 		for (size_t rr = 0; rr < lines.Count(); rr++) {
-			lines[rr].Reverse();
+			if (get_uformat() == U_UTF8)
+				lines[rr].ReverseUTF8();
+			else
+				lines[rr].Reverse();
 			line_length = wgettextwidth_compensate(lines[rr].GetCStr(), fonnt);
 			if (line_length > _G(longestline))
 				_G(longestline) = line_length;
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 281778b58e..0f9d6f6dc7 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -206,6 +206,8 @@ public:
 	int _trans_blend_green = 0;
 	int _trans_blend_blue = 0;
 	BlenderMode __blender_mode = kRgbToRgbBlender;
+	/* current format information and worker routines */
+	int _utype = U_UTF8;
 
 	/* default palette structures */
 	PALETTE _black_palette;
diff --git a/engines/ags/lib/allegro/unicode.cpp b/engines/ags/lib/allegro/unicode.cpp
index 7a31f26528..29508a1d75 100644
--- a/engines/ags/lib/allegro/unicode.cpp
+++ b/engines/ags/lib/allegro/unicode.cpp
@@ -20,13 +20,35 @@
  *
  */
 
-#include "ags/lib/allegro/unicode.h"
 #include "common/textconsole.h"
+#include "ags/lib/allegro/unicode.h"
+#include "ags/globals.h"
 
 namespace AGS3 {
 
+/* ugetc: */
+int (*ugetc)(const char *s) = utf8_getc;
+/* ugetxc: */
+int (*ugetx)(char **s) = utf8_getx;
+/* ugetxc: */
+int (*ugetxc)(const char **s) = (int (*)(const char **)) utf8_getx;
+/* usetc: */
+int (*usetc)(char *s, int c) = utf8_setc;
+/* uwidth: */
+int (*uwidth)(const char *s) = utf8_width;
+/* ucwidth: */
+int (*ucwidth)(int c) = utf8_cwidth;
+/* uisok: */
+int (*uisok)(int c) = utf8_isok;
+
+
 void set_uformat(int format) {
 	// TODO: implementation
+	_G(utype) = format;
+}
+
+int get_uformat(void) {
+	return _G(utype);
 }
 
 size_t ustrsize(const char *s) {
diff --git a/engines/ags/lib/allegro/unicode.h b/engines/ags/lib/allegro/unicode.h
index c87f8e0dc9..1dc1070985 100644
--- a/engines/ags/lib/allegro/unicode.h
+++ b/engines/ags/lib/allegro/unicode.h
@@ -33,7 +33,15 @@ namespace AGS3 {
 #define U_UTF8          AL_ID('U','T','F','8')
 #define U_CURRENT       AL_ID('c','u','r','.')
 
+/* set_uformat:
+ *  Selects a new text encoding format.
+ */
 extern void set_uformat(int format);
+
+/* get_uformat:
+ *  Returns the current text encoding format.
+ */
+extern int get_uformat();
 extern size_t ustrsize(const char *s);
 
 /* UTF-8 support functions
@@ -45,6 +53,21 @@ int utf8_width(const char *s);
 int utf8_cwidth(int c);
 int utf8_isok(int c);
 
+/* ugetc: */
+extern int (*ugetc)(const char *s);
+/* ugetxc: */
+extern int (*ugetx)(char **s);
+/* ugetxc: */
+extern int (*ugetxc)(const char **s);
+/* usetc: */
+extern int (*usetc)(char *s, int c);
+/* uwidth: */
+extern int (*uwidth)(const char *s);
+/* ucwidth: */
+extern int (*ucwidth)(int c);
+/* uisok: */
+extern int (*uisok)(int c);
+
 } // namespace AGS3
 
 #endif
diff --git a/engines/ags/shared/font/fonts.cpp b/engines/ags/shared/font/fonts.cpp
index 70fddd9f60..eec9f88bcd 100644
--- a/engines/ags/shared/font/fonts.cpp
+++ b/engines/ags/shared/font/fonts.cpp
@@ -50,8 +50,8 @@ Font::Font()
 	, Renderer2(nullptr) {
 }
 
-} // Common
-} // AGS
+} // namespace Shared
+} // namespace AGS
 
 FontInfo::FontInfo()
 	: Flags(0)
@@ -84,7 +84,7 @@ bool font_first_renderer_loaded() {
 }
 
 bool is_font_loaded(size_t fontNumber) {
-	return fontNumber < _GP(fonts).size() && _GP(fonts)[fontNumber].Renderer != nullptr;
+	return fontNumber < _GP(fonts).size() && _GP(fonts)[fontNumber].Renderer != nullptr;;
 }
 
 IAGSFontRenderer *font_replace_renderer(size_t fontNumber, IAGSFontRenderer *renderer) {
@@ -170,6 +170,15 @@ bool use_default_linespacing(size_t fontNumber) {
 	return _GP(fonts)[fontNumber].Info.LineSpacing == 0;
 }
 
+// Project-dependent implementation
+extern int wgettextwidth_compensate(const char *tex, int font);
+
+namespace AGS {
+namespace Common {
+SplitLines Lines;
+}
+}
+
 // Replaces AGS-specific linebreak tags with common '\n'
 void unescape_script_string(const char *cstr, std::vector<char> &out) {
 	out.clear();
@@ -179,6 +188,8 @@ void unescape_script_string(const char *cstr, std::vector<char> &out) {
 		cstr++;
 	}
 	// Replace all other occurrences as they're found
+	// NOTE: we do not need to decode utf8 here, because
+	// we are only searching for low-code ascii chars.
 	const char *off;
 	for (off = cstr; *off; ++off) {
 		if (*off != '[') continue;
@@ -202,76 +213,86 @@ size_t split_lines(const char *todis, SplitLines &lines, int wii, int fonnt, siz
 	// It's hard to tell how cruicial it is for the game looks, so research may be needed.
 	// TODO: IMHO this should rely not on game format, but script API level, because it
 	// defines necessary adjustments to game scripts. If you want to fix this, find a way to
-	// pass this flag here all the way from _GP(game).options[OPT_BASESCRIPTAPI] (or game format).
+	// pass this flag here all the way from game.options[OPT_BASESCRIPTAPI] (or game format).
 	//
-	// if (_GP(game).options[OPT_BASESCRIPTAPI] < $Your current version$)
+	// if (game.options[OPT_BASESCRIPTAPI] < $Your current version$)
 	wii -= 1;
 
 	lines.Reset();
 	unescape_script_string(todis, lines.LineBuf);
 	char *theline = &lines.LineBuf.front();
 
-	size_t i = 0;
-	size_t splitAt;
-	char nextCharWas;
+	char *scan_ptr = theline;
+	char *prev_ptr = theline;
+	char *last_whitespace = nullptr;
 	while (1) {
-		splitAt = (size_t)-1;
+		char *split_at = nullptr;
 
-		if (theline[i] == 0) {
+		if (*scan_ptr == 0) {
 			// end of the text, add the last line if necessary
-			if (i > 0) {
+			if (scan_ptr > theline) {
 				lines.Add(theline);
 			}
 			break;
 		}
 
-		// temporarily terminate the line here and test its width
-		nextCharWas = theline[i + 1];
-		theline[i + 1] = 0;
+		if (*scan_ptr == ' ')
+			last_whitespace = scan_ptr;
 
 		// force end of line with the \n character
-		if (theline[i] == '\n')
-			splitAt = i;
-		// otherwise, see if we are too wide
-		else if (wgettextwidth_compensate(theline, fonnt) > wii) {
-			int endline = i;
-			while ((theline[endline] != ' ') && (endline > 0))
-				endline--;
-
-			// single very wide word, display as much as possible
-			if (endline == 0)
-				endline = i - 1;
-
-			splitAt = endline;
-		}
+		if (*scan_ptr == '\n') {
+			split_at = scan_ptr;
+			// otherwise, see if we are too wide
+		} else {
+			// temporarily terminate the line in the *next* char and test its width
+			char *next_ptr = scan_ptr;
+			ugetx(&next_ptr);
+			const int next_chwas = ugetc(next_ptr);
+			*next_ptr = 0;
+
+			if (wgettextwidth_compensate(theline, fonnt) > wii) {
+				// line is too wide, order the split
+				if (last_whitespace)
+					// revert to the last whitespace
+					split_at = last_whitespace;
+				else
+					// single very wide word, display as much as possible
+					split_at = prev_ptr;
+			}
 
-		// restore the character that was there before
-		theline[i + 1] = nextCharWas;
+			// restore the character that was there before
+			usetc(next_ptr, next_chwas);
+		}
 
-		if (splitAt != (size_t)-1) {
-			if (splitAt == 0 && !((theline[0] == ' ') || (theline[0] == '\n'))) {
+		if (split_at == nullptr) {
+			prev_ptr = scan_ptr;
+			ugetx(&scan_ptr);
+		} else {
+			// check if even one char cannot fit...
+			if (split_at == theline && !((*theline == ' ') || (*theline == '\n'))) {
 				// cannot split with current width restriction
 				lines.Reset();
 				break;
 			}
-			// add this line
-			nextCharWas = theline[splitAt];
-			theline[splitAt] = 0;
+			// add this line; do the temporary terminator trick again
+			const int next_chwas = *split_at;
+			*split_at = 0;
 			lines.Add(theline);
-			theline[splitAt] = nextCharWas;
+			usetc(split_at, next_chwas);
+			// check if too many lines
 			if (lines.Count() >= max_lines) {
 				lines[lines.Count() - 1].Append("...");
 				break;
 			}
-			// the next line starts from here
-			theline += splitAt;
+			// the next line starts from the split point
+			theline = split_at;
 			// skip the space or new line that caused the line break
-			if ((theline[0] == ' ') || (theline[0] == '\n'))
+			if ((*theline == ' ') || (*theline == '\n'))
 				theline++;
-			i = (size_t)-1;
+			scan_ptr = theline;
+			prev_ptr = theline;
+			last_whitespace = nullptr;
 		}
-
-		i++;
 	}
 	return lines.Count();
 }
diff --git a/engines/ags/shared/util/string.cpp b/engines/ags/shared/util/string.cpp
index f4866058ab..fd4c60e1e6 100644
--- a/engines/ags/shared/util/string.cpp
+++ b/engines/ags/shared/util/string.cpp
@@ -681,6 +681,30 @@ void String::Reverse() {
 	}
 }
 
+void String::ReverseUTF8() {
+	if (_len <= 1)
+		return; // nothing to reverse if 1 char or less
+	// TODO: may this be optimized to not alloc new buffer? or dont care
+	char *newstr = new char[_len + 1];
+	for (char *fw = _cstr, *fw2 = _cstr + 1,
+		*bw = _cstr + _len - 1, *bw2 = _cstr + _len;
+		fw <= bw; // FIXME: <= catches odd middle char, optimize?
+		fw = fw2++, bw2 = bw--) {
+		// find end of next character forwards
+		for (; (fw2 < bw) && ((*fw2 & 0xC0) == 0x80); ++fw2);
+		// find beginning of the prev character backwards
+		for (; (bw > fw) && ((*bw & 0xC0) == 0x80); --bw);
+		// put these in opposite sides on the new buffer
+		char *fw_place = newstr + (_cstr + _len - bw2);
+		char *bw_place = newstr + _len - (fw2 - _cstr);
+		memcpy(fw_place, bw, bw2 - bw);
+		if (fw != bw) // FIXME, optimize?
+			memcpy(bw_place, fw, fw2 - fw);
+	}
+	newstr[_len] = 0;
+	SetString(newstr);
+}
+
 void String::SetAt(size_t index, char c) {
 	if ((index < _len) && c) {
 		BecomeUnique();
diff --git a/engines/ags/shared/util/string.h b/engines/ags/shared/util/string.h
index e784e5d2e8..8d41d856c4 100644
--- a/engines/ags/shared/util/string.h
+++ b/engines/ags/shared/util/string.h
@@ -342,6 +342,10 @@ public:
 	}
 	// Reverses the string
 	void    Reverse();
+	// Reverse the multibyte unicode string
+	// FIXME: name? invent some consistent naming for necessary multibyte funcs,
+	// proper utf8 support where necessary
+	void    ReverseUTF8();
 	// Overwrite the Nth character of the string; does not change string's length
 	void    SetAt(size_t index, char c);
 	// Makes a new string by copying up to N chars from C-string


Commit: e3a9f13ae0a056067095d401022560243d4f9dc0
    https://github.com/scummvm/scummvm/commit/e3a9f13ae0a056067095d401022560243d4f9dc0
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:36-07:00

Commit Message:
AGS: Added needed Allegro unicode methods

Changed paths:
    engines/ags/lib/allegro/unicode.cpp
    engines/ags/lib/allegro/unicode.h


diff --git a/engines/ags/lib/allegro/unicode.cpp b/engines/ags/lib/allegro/unicode.cpp
index 29508a1d75..36626c9cdd 100644
--- a/engines/ags/lib/allegro/unicode.cpp
+++ b/engines/ags/lib/allegro/unicode.cpp
@@ -55,10 +55,7 @@ size_t ustrsize(const char *s) {
 	return strlen(s);
 }
 
-/* utf8_getc:
- * Reads a character from a UTF - 8 string.
- */
-/*static*/ int utf8_getc(const char *s) {
+int utf8_getc(const char *s) {
 	int c = *((const unsigned char *)(s++));
 	int n, t;
 
@@ -82,12 +79,7 @@ size_t ustrsize(const char *s) {
 	return c;
 }
 
-
-
-/* utf8_getx:
- *  Reads a character from a UTF-8 string, advancing the pointer position.
- */
-/*static*/ int utf8_getx(char **s) {
+int utf8_getx(char **s) {
 	int c = *((unsigned char *)((*s)++));
 	int n, t;
 
@@ -113,12 +105,7 @@ size_t ustrsize(const char *s) {
 	return c;
 }
 
-
-
-/* utf8_setc:
- *  Sets a character in a UTF-8 string.
- */
-/*static*/ int utf8_setc(char *s, int c) {
+int utf8_setc(char *s, int c) {
 	int size, bits, b, i;
 
 	if (c < 128) {
@@ -152,12 +139,7 @@ size_t ustrsize(const char *s) {
 	return size;
 }
 
-
-
-/* utf8_width:
- *  Returns the width of a UTF-8 character.
- */
-/*static*/ int utf8_width(const char *s) {
+int utf8_width(const char *s) {
 	int c = *((const unsigned char *)s);
 	int n = 1;
 
@@ -169,12 +151,7 @@ size_t ustrsize(const char *s) {
 	return n;
 }
 
-
-
-/* utf8_cwidth:
- *  Returns the width of a UTF-8 character.
- */
-/*static*/ int utf8_cwidth(int c) {
+int utf8_cwidth(int c) {
 	int size, bits, b;
 
 	if (c < 128)
@@ -195,11 +172,1044 @@ size_t ustrsize(const char *s) {
 	return size;
 }
 
-/* utf8_isok:
- *  Checks whether this character can be encoded in UTF-8 format.
- */
-/*static*/ int utf8_isok(int c) {
+int utf8_isok(int c) {
 	return true;
 }
 
+int ustrlen(const char *s) {
+	int c = 0;
+	assert(s);
+
+	while (ugetxc(&s))
+		c++;
+
+	return c;
+}
+
+int utolower(int c) {
+	if ((c >= 65 && c <= 90) ||
+		(c >= 192 && c <= 214) ||
+		(c >= 216 && c <= 222) ||
+		(c >= 913 && c <= 929) ||
+		(c >= 931 && c <= 939) ||
+		(c >= 1040 && c <= 1071))
+		return c + 32;
+	if ((c >= 393 && c <= 394))
+		return c + 205;
+	if ((c >= 433 && c <= 434))
+		return c + 217;
+	if ((c >= 904 && c <= 906))
+		return c + 37;
+	if ((c >= 910 && c <= 911))
+		return c + 63;
+	if ((c >= 1025 && c <= 1036) ||
+		(c >= 1038 && c <= 1039))
+		return c + 80;
+	if ((c >= 1329 && c <= 1366) ||
+		(c >= 4256 && c <= 4293))
+		return c + 48;
+	if ((c >= 7944 && c <= 7951) ||
+		(c >= 7960 && c <= 7965) ||
+		(c >= 7976 && c <= 7983) ||
+		(c >= 7992 && c <= 7999) ||
+		(c >= 8008 && c <= 8013) ||
+		(c >= 8040 && c <= 8047) ||
+		(c >= 8072 && c <= 8079) ||
+		(c >= 8088 && c <= 8095) ||
+		(c >= 8104 && c <= 8111) ||
+		(c >= 8120 && c <= 8121) ||
+		(c >= 8152 && c <= 8153) ||
+		(c >= 8168 && c <= 8169))
+		return c + -8;
+	if ((c >= 8122 && c <= 8123))
+		return c + -74;
+	if ((c >= 8136 && c <= 8139))
+		return c + -86;
+	if ((c >= 8154 && c <= 8155))
+		return c + -100;
+	if ((c >= 8170 && c <= 8171))
+		return c + -112;
+	if ((c >= 8184 && c <= 8185))
+		return c + -128;
+	if ((c >= 8186 && c <= 8187))
+		return c + -126;
+	if ((c >= 8544 && c <= 8559))
+		return c + 16;
+	if ((c >= 9398 && c <= 9423))
+		return c + 26;
+
+	switch (c) {
+	case 256:
+	case 258:
+	case 260:
+	case 262:
+	case 264:
+	case 266:
+	case 268:
+	case 270:
+	case 272:
+	case 274:
+	case 276:
+	case 278:
+	case 280:
+	case 282:
+	case 284:
+	case 286:
+	case 288:
+	case 290:
+	case 292:
+	case 294:
+	case 296:
+	case 298:
+	case 300:
+	case 302:
+	case 306:
+	case 308:
+	case 310:
+	case 313:
+	case 315:
+	case 317:
+	case 319:
+	case 321:
+	case 323:
+	case 325:
+	case 327:
+	case 330:
+	case 332:
+	case 334:
+	case 336:
+	case 338:
+	case 340:
+	case 342:
+	case 344:
+	case 346:
+	case 348:
+	case 350:
+	case 352:
+	case 354:
+	case 356:
+	case 358:
+	case 360:
+	case 362:
+	case 364:
+	case 366:
+	case 368:
+	case 370:
+	case 372:
+	case 374:
+	case 377:
+	case 379:
+	case 381:
+	case 386:
+	case 388:
+	case 391:
+	case 395:
+	case 401:
+	case 408:
+	case 416:
+	case 418:
+	case 420:
+	case 423:
+	case 428:
+	case 431:
+	case 435:
+	case 437:
+	case 440:
+	case 444:
+	case 453:
+	case 456:
+	case 459:
+	case 461:
+	case 463:
+	case 465:
+	case 467:
+	case 469:
+	case 471:
+	case 473:
+	case 475:
+	case 478:
+	case 480:
+	case 482:
+	case 484:
+	case 486:
+	case 488:
+	case 490:
+	case 492:
+	case 494:
+	case 498:
+	case 500:
+	case 506:
+	case 508:
+	case 510:
+	case 512:
+	case 514:
+	case 516:
+	case 518:
+	case 520:
+	case 522:
+	case 524:
+	case 526:
+	case 528:
+	case 530:
+	case 532:
+	case 534:
+	case 994:
+	case 996:
+	case 998:
+	case 1000:
+	case 1002:
+	case 1004:
+	case 1006:
+	case 1120:
+	case 1122:
+	case 1124:
+	case 1126:
+	case 1128:
+	case 1130:
+	case 1132:
+	case 1134:
+	case 1136:
+	case 1138:
+	case 1140:
+	case 1142:
+	case 1144:
+	case 1146:
+	case 1148:
+	case 1150:
+	case 1152:
+	case 1168:
+	case 1170:
+	case 1172:
+	case 1174:
+	case 1176:
+	case 1178:
+	case 1180:
+	case 1182:
+	case 1184:
+	case 1186:
+	case 1188:
+	case 1190:
+	case 1192:
+	case 1194:
+	case 1196:
+	case 1198:
+	case 1200:
+	case 1202:
+	case 1204:
+	case 1206:
+	case 1208:
+	case 1210:
+	case 1212:
+	case 1214:
+	case 1217:
+	case 1219:
+	case 1223:
+	case 1227:
+	case 1232:
+	case 1234:
+	case 1236:
+	case 1238:
+	case 1240:
+	case 1242:
+	case 1244:
+	case 1246:
+	case 1248:
+	case 1250:
+	case 1252:
+	case 1254:
+	case 1256:
+	case 1258:
+	case 1262:
+	case 1264:
+	case 1266:
+	case 1268:
+	case 1272:
+	case 7680:
+	case 7682:
+	case 7684:
+	case 7686:
+	case 7688:
+	case 7690:
+	case 7692:
+	case 7694:
+	case 7696:
+	case 7698:
+	case 7700:
+	case 7702:
+	case 7704:
+	case 7706:
+	case 7708:
+	case 7710:
+	case 7712:
+	case 7714:
+	case 7716:
+	case 7718:
+	case 7720:
+	case 7722:
+	case 7724:
+	case 7726:
+	case 7728:
+	case 7730:
+	case 7732:
+	case 7734:
+	case 7736:
+	case 7738:
+	case 7740:
+	case 7742:
+	case 7744:
+	case 7746:
+	case 7748:
+	case 7750:
+	case 7752:
+	case 7754:
+	case 7756:
+	case 7758:
+	case 7760:
+	case 7762:
+	case 7764:
+	case 7766:
+	case 7768:
+	case 7770:
+	case 7772:
+	case 7774:
+	case 7776:
+	case 7778:
+	case 7780:
+	case 7782:
+	case 7784:
+	case 7786:
+	case 7788:
+	case 7790:
+	case 7792:
+	case 7794:
+	case 7796:
+	case 7798:
+	case 7800:
+	case 7802:
+	case 7804:
+	case 7806:
+	case 7808:
+	case 7810:
+	case 7812:
+	case 7814:
+	case 7816:
+	case 7818:
+	case 7820:
+	case 7822:
+	case 7824:
+	case 7826:
+	case 7828:
+	case 7840:
+	case 7842:
+	case 7844:
+	case 7846:
+	case 7848:
+	case 7850:
+	case 7852:
+	case 7854:
+	case 7856:
+	case 7858:
+	case 7860:
+	case 7862:
+	case 7864:
+	case 7866:
+	case 7868:
+	case 7870:
+	case 7872:
+	case 7874:
+	case 7876:
+	case 7878:
+	case 7880:
+	case 7882:
+	case 7884:
+	case 7886:
+	case 7888:
+	case 7890:
+	case 7892:
+	case 7894:
+	case 7896:
+	case 7898:
+	case 7900:
+	case 7902:
+	case 7904:
+	case 7906:
+	case 7908:
+	case 7910:
+	case 7912:
+	case 7914:
+	case 7916:
+	case 7918:
+	case 7920:
+	case 7922:
+	case 7924:
+	case 7926:
+	case 7928:
+		return c + 1;
+	case 304:
+		return c + -199;
+	case 376:
+		return c + -121;
+	case 385:
+		return c + 210;
+	case 390:
+		return c + 206;
+	case 398:
+		return c + 79;
+	case 399:
+		return c + 202;
+	case 400:
+		return c + 203;
+	case 403:
+		return c + 205;
+	case 404:
+		return c + 207;
+	case 406:
+	case 412:
+		return c + 211;
+	case 407:
+		return c + 209;
+	case 413:
+		return c + 213;
+	case 415:
+		return c + 214;
+	case 422:
+	case 425:
+	case 430:
+		return c + 218;
+	case 439:
+		return c + 219;
+	case 452:
+	case 455:
+	case 458:
+	case 497:
+		return c + 2;
+	case 902:
+		return c + 38;
+	case 908:
+		return c + 64;
+	case 8025:
+	case 8027:
+	case 8029:
+	case 8031:
+		return c + -8;
+	case 8124:
+	case 8140:
+	case 8188:
+		return c + -9;
+	case 8172:
+		return c + -7;
+	default:
+		return c;
+	}
+}
+
+int utoupper(int c) {
+	if ((c >= 97 && c <= 122) ||
+		(c >= 224 && c <= 246) ||
+		(c >= 248 && c <= 254) ||
+		(c >= 945 && c <= 961) ||
+		(c >= 963 && c <= 971) ||
+		(c >= 1072 && c <= 1103))
+		return c + -32;
+	if ((c >= 598 && c <= 599))
+		return c + -205;
+	if ((c >= 650 && c <= 651))
+		return c + -217;
+	if ((c >= 941 && c <= 943))
+		return c + -37;
+	if ((c >= 973 && c <= 974))
+		return c + -63;
+	if ((c >= 1105 && c <= 1116) ||
+		(c >= 1118 && c <= 1119))
+		return c + -80;
+	if ((c >= 1377 && c <= 1414))
+		return c + -48;
+	if ((c >= 7936 && c <= 7943) ||
+		(c >= 7952 && c <= 7957) ||
+		(c >= 7968 && c <= 7975) ||
+		(c >= 7984 && c <= 7991) ||
+		(c >= 8000 && c <= 8005) ||
+		(c >= 8032 && c <= 8039) ||
+		(c >= 8064 && c <= 8071) ||
+		(c >= 8080 && c <= 8087) ||
+		(c >= 8096 && c <= 8103) ||
+		(c >= 8112 && c <= 8113) ||
+		(c >= 8144 && c <= 8145) ||
+		(c >= 8160 && c <= 8161))
+		return c + 8;
+	if ((c >= 8048 && c <= 8049))
+		return c + 74;
+	if ((c >= 8050 && c <= 8053))
+		return c + 86;
+	if ((c >= 8054 && c <= 8055))
+		return c + 100;
+	if ((c >= 8056 && c <= 8057))
+		return c + 128;
+	if ((c >= 8058 && c <= 8059))
+		return c + 112;
+	if ((c >= 8060 && c <= 8061))
+		return c + 126;
+	if ((c >= 8560 && c <= 8575))
+		return c + -16;
+	if ((c >= 9424 && c <= 9449))
+		return c + -26;
+
+	switch (c) {
+	case 255:
+		return c + 121;
+	case 257:
+	case 259:
+	case 261:
+	case 263:
+	case 265:
+	case 267:
+	case 269:
+	case 271:
+	case 273:
+	case 275:
+	case 277:
+	case 279:
+	case 281:
+	case 283:
+	case 285:
+	case 287:
+	case 289:
+	case 291:
+	case 293:
+	case 295:
+	case 297:
+	case 299:
+	case 301:
+	case 303:
+	case 307:
+	case 309:
+	case 311:
+	case 314:
+	case 316:
+	case 318:
+	case 320:
+	case 322:
+	case 324:
+	case 326:
+	case 328:
+	case 331:
+	case 333:
+	case 335:
+	case 337:
+	case 339:
+	case 341:
+	case 343:
+	case 345:
+	case 347:
+	case 349:
+	case 351:
+	case 353:
+	case 355:
+	case 357:
+	case 359:
+	case 361:
+	case 363:
+	case 365:
+	case 367:
+	case 369:
+	case 371:
+	case 373:
+	case 375:
+	case 378:
+	case 380:
+	case 382:
+	case 387:
+	case 389:
+	case 392:
+	case 396:
+	case 402:
+	case 409:
+	case 417:
+	case 419:
+	case 421:
+	case 424:
+	case 429:
+	case 432:
+	case 436:
+	case 438:
+	case 441:
+	case 445:
+	case 453:
+	case 456:
+	case 459:
+	case 462:
+	case 464:
+	case 466:
+	case 468:
+	case 470:
+	case 472:
+	case 474:
+	case 476:
+	case 479:
+	case 481:
+	case 483:
+	case 485:
+	case 487:
+	case 489:
+	case 491:
+	case 493:
+	case 495:
+	case 498:
+	case 501:
+	case 507:
+	case 509:
+	case 511:
+	case 513:
+	case 515:
+	case 517:
+	case 519:
+	case 521:
+	case 523:
+	case 525:
+	case 527:
+	case 529:
+	case 531:
+	case 533:
+	case 535:
+	case 995:
+	case 997:
+	case 999:
+	case 1001:
+	case 1003:
+	case 1005:
+	case 1007:
+	case 1121:
+	case 1123:
+	case 1125:
+	case 1127:
+	case 1129:
+	case 1131:
+	case 1133:
+	case 1135:
+	case 1137:
+	case 1139:
+	case 1141:
+	case 1143:
+	case 1145:
+	case 1147:
+	case 1149:
+	case 1151:
+	case 1153:
+	case 1169:
+	case 1171:
+	case 1173:
+	case 1175:
+	case 1177:
+	case 1179:
+	case 1181:
+	case 1183:
+	case 1185:
+	case 1187:
+	case 1189:
+	case 1191:
+	case 1193:
+	case 1195:
+	case 1197:
+	case 1199:
+	case 1201:
+	case 1203:
+	case 1205:
+	case 1207:
+	case 1209:
+	case 1211:
+	case 1213:
+	case 1215:
+	case 1218:
+	case 1220:
+	case 1224:
+	case 1228:
+	case 1233:
+	case 1235:
+	case 1237:
+	case 1239:
+	case 1241:
+	case 1243:
+	case 1245:
+	case 1247:
+	case 1249:
+	case 1251:
+	case 1253:
+	case 1255:
+	case 1257:
+	case 1259:
+	case 1263:
+	case 1265:
+	case 1267:
+	case 1269:
+	case 1273:
+	case 7681:
+	case 7683:
+	case 7685:
+	case 7687:
+	case 7689:
+	case 7691:
+	case 7693:
+	case 7695:
+	case 7697:
+	case 7699:
+	case 7701:
+	case 7703:
+	case 7705:
+	case 7707:
+	case 7709:
+	case 7711:
+	case 7713:
+	case 7715:
+	case 7717:
+	case 7719:
+	case 7721:
+	case 7723:
+	case 7725:
+	case 7727:
+	case 7729:
+	case 7731:
+	case 7733:
+	case 7735:
+	case 7737:
+	case 7739:
+	case 7741:
+	case 7743:
+	case 7745:
+	case 7747:
+	case 7749:
+	case 7751:
+	case 7753:
+	case 7755:
+	case 7757:
+	case 7759:
+	case 7761:
+	case 7763:
+	case 7765:
+	case 7767:
+	case 7769:
+	case 7771:
+	case 7773:
+	case 7775:
+	case 7777:
+	case 7779:
+	case 7781:
+	case 7783:
+	case 7785:
+	case 7787:
+	case 7789:
+	case 7791:
+	case 7793:
+	case 7795:
+	case 7797:
+	case 7799:
+	case 7801:
+	case 7803:
+	case 7805:
+	case 7807:
+	case 7809:
+	case 7811:
+	case 7813:
+	case 7815:
+	case 7817:
+	case 7819:
+	case 7821:
+	case 7823:
+	case 7825:
+	case 7827:
+	case 7829:
+	case 7841:
+	case 7843:
+	case 7845:
+	case 7847:
+	case 7849:
+	case 7851:
+	case 7853:
+	case 7855:
+	case 7857:
+	case 7859:
+	case 7861:
+	case 7863:
+	case 7865:
+	case 7867:
+	case 7869:
+	case 7871:
+	case 7873:
+	case 7875:
+	case 7877:
+	case 7879:
+	case 7881:
+	case 7883:
+	case 7885:
+	case 7887:
+	case 7889:
+	case 7891:
+	case 7893:
+	case 7895:
+	case 7897:
+	case 7899:
+	case 7901:
+	case 7903:
+	case 7905:
+	case 7907:
+	case 7909:
+	case 7911:
+	case 7913:
+	case 7915:
+	case 7917:
+	case 7919:
+	case 7921:
+	case 7923:
+	case 7925:
+	case 7927:
+	case 7929:
+		return c + -1;
+	case 305:
+		return c + -232;
+	case 383:
+		return c + -300;
+	case 454:
+	case 457:
+	case 460:
+	case 499:
+		return c + -2;
+	case 477:
+	case 1010:
+		return c + -79;
+	case 595:
+		return c + -210;
+	case 596:
+		return c + -206;
+	case 601:
+		return c + -202;
+	case 603:
+		return c + -203;
+	case 608:
+		return c + -205;
+	case 611:
+		return c + -207;
+	case 616:
+		return c + -209;
+	case 617:
+	case 623:
+		return c + -211;
+	case 626:
+		return c + -213;
+	case 629:
+		return c + -214;
+	case 640:
+	case 643:
+	case 648:
+		return c + -218;
+	case 658:
+		return c + -219;
+	case 837:
+		return c + 84;
+	case 940:
+		return c + -38;
+	case 962:
+		return c + -31;
+	case 972:
+		return c + -64;
+	case 976:
+		return c + -62;
+	case 977:
+		return c + -57;
+	case 981:
+		return c + -47;
+	case 982:
+		return c + -54;
+	case 1008:
+		return c + -86;
+	case 1009:
+		return c + -80;
+	case 7835:
+		return c + -59;
+	case 8017:
+	case 8019:
+	case 8021:
+	case 8023:
+		return c + 8;
+	case 8115:
+	case 8131:
+	case 8179:
+		return c + 9;
+	case 8126:
+		return c + -7205;
+	case 8165:
+		return c + 7;
+	default:
+		return c;
+	}
+}
+
+int ustrcmp(const char *s1, const char *s2) {
+	int c1, c2;
+	assert(s1);
+	assert(s2);
+
+	for (;;) {
+		c1 = ugetxc(&s1);
+		c2 = ugetxc(&s2);
+
+		if (c1 != c2)
+			return c1 - c2;
+
+		if (!c1)
+			return 0;
+	}
+}
+
+int ustrncmp(const char *s1, const char *s2, int n) {
+	int c1, c2;
+	assert(s1);
+	assert(s2);
+
+	if (n <= 0)
+		return 0;
+
+	for (;;) {
+		c1 = ugetxc(&s1);
+		c2 = ugetxc(&s2);
+
+		if (c1 != c2)
+			return c1 - c2;
+
+		if ((!c1) || (--n <= 0))
+			return 0;
+	}
+}
+
+int ustricmp(const char *s1, const char *s2) {
+	int c1, c2;
+	assert(s1);
+	assert(s2);
+
+	for (;;) {
+		c1 = utolower(ugetxc(&s1));
+		c2 = utolower(ugetxc(&s2));
+
+		if (c1 != c2)
+			return c1 - c2;
+
+		if (!c1)
+			return 0;
+	}
+}
+
+int ustrnicmp(const char *s1, const char *s2, int n) {
+	int c1, c2;
+	assert(s1);
+	assert(s2);
+
+	if (n <= 0)
+		return 0;
+
+	for (;;) {
+		c1 = utolower(ugetxc(&s1));
+		c2 = utolower(ugetxc(&s2));
+
+		if (c1 != c2)
+			return c1 - c2;
+
+		if ((!c1) || (--n <= 0))
+			return 0;
+	}
+}
+
+int uoffset(const char *s, int index) {
+	const char *orig = s;
+	const char *last;
+	assert(s);
+
+	if (index < 0)
+		index += ustrlen(s);
+
+	while (index-- > 0) {
+		last = s;
+		if (!ugetxc(&s)) {
+			s = last;
+			break;
+		}
+	}
+
+	return (long)s - (long)orig;
+}
+
+char *ustrlwr(char *s) {
+	int pos = 0;
+	int c, lc;
+	assert(s);
+
+	while ((c = ugetc(s + pos)) != 0) {
+		lc = utolower(c);
+
+		if (lc != c)
+			usetat(s + pos, 0, lc);
+
+		pos += uwidth(s + pos);
+	}
+
+	return s;
+}
+
+char *ustrupr(char *s) {
+	int pos = 0;
+	int c, uc;
+	assert(s);
+
+	while ((c = ugetc(s + pos)) != 0) {
+		uc = utoupper(c);
+
+		if (uc != c)
+			usetat(s + pos, 0, uc);
+
+		pos += uwidth(s + pos);
+	}
+
+	return s;
+}
+
+char *ustrstr(const char *s1, const char *s2) {
+	int len;
+	assert(s1);
+	assert(s2);
+
+	len = ustrlen(s2);
+	while (ugetc(s1)) {
+		if (ustrncmp(s1, s2, len) == 0)
+			return (char *)s1;
+
+		s1 += uwidth(s1);
+	}
+
+	return NULL;
+}
+
+int usetat(char *s, int index, int c) {
+	int oldw, neww;
+	assert(s);
+
+	s += uoffset(s, index);
+
+	oldw = uwidth(s);
+	neww = ucwidth(c);
+
+	if (oldw != neww)
+		memmove(s + neww, s + oldw, ustrsizez(s + oldw));
+
+	usetc(s, c);
+
+	return neww - oldw;
+}
+
+int ustrsizez(const char *s) {
+	const char *orig = s;
+	assert(s);
+
+	do {
+	} while (ugetxc(&s) != 0);
+
+	return (long)s - (long)orig;
+}
+
 } // namespace AGS3
diff --git a/engines/ags/lib/allegro/unicode.h b/engines/ags/lib/allegro/unicode.h
index 1dc1070985..5ea82c03f9 100644
--- a/engines/ags/lib/allegro/unicode.h
+++ b/engines/ags/lib/allegro/unicode.h
@@ -33,16 +33,6 @@ namespace AGS3 {
 #define U_UTF8          AL_ID('U','T','F','8')
 #define U_CURRENT       AL_ID('c','u','r','.')
 
-/* set_uformat:
- *  Selects a new text encoding format.
- */
-extern void set_uformat(int format);
-
-/* get_uformat:
- *  Returns the current text encoding format.
- */
-extern int get_uformat();
-extern size_t ustrsize(const char *s);
 
 /* UTF-8 support functions
  */
@@ -68,6 +58,73 @@ extern int (*ucwidth)(int c);
 /* uisok: */
 extern int (*uisok)(int c);
 
+/* set_uformat:
+ *  Selects a new text encoding format.
+ */
+extern void set_uformat(int format);
+
+/* get_uformat:
+ *  Returns the current text encoding format.
+ */
+extern int get_uformat();
+extern size_t ustrsize(const char *s);
+/* &nicode string length
+ */
+extern int ustrlen(const char *s);
+/* utolower:
+ *  Unicode-aware version of the ANSI tolower() function.
+ */
+extern int utolower(int c);
+/* utoupper:
+ *  Unicode-aware version of the ANSI toupper() function.
+ */
+extern int utoupper(int c);
+/* Unicode string compare
+ */
+extern int ustrcmp(const char *s1, const char *s2);
+ /* ustricmp:
+  *  Unicode-aware version of the DJGPP stricmp() function.
+  */
+extern int ustricmp(const char *s1, const char *s2);
+/* ustrncmp:
+ *  Unicode-aware version of the ANSI strncmp() function.
+ */
+extern int ustrncmp(const char *s1, const char *s2, int n);
+/* ustrnicmp:
+ *  Unicode-aware version of the DJGPP strnicmp() function.
+ */
+extern int ustrnicmp(const char *s1, const char *s2, int n);
+/* uoffset:
+ *  Returns the offset in bytes from the start of the string to the
+ *  character at the specified index. If the index is negative, counts
+ *  backward from the end of the string (-1 returns an offset to the
+ *  last character).
+ */
+extern int uoffset(const char *s, int index);
+/* ustrlwr:
+ *  Unicode-aware version of the ANSI strlwr() function.
+ */
+extern char *ustrlwr(char *s);
+/* ustrupr:
+ *  Unicode-aware version of the ANSI strupr() function.
+ */
+extern char *ustrupr(char *s);
+/* ustrstr:
+ *  Unicode-aware version of the ANSI strstr() function.
+ */
+extern char *ustrstr(const char *s1, const char *s2);
+/* usetat:
+ *  Modifies the character at the specified index within the string,
+ *  handling adjustments for variable width data. Returns how far the
+ *  rest of the string was moved.
+ */
+int usetat(char *s, int index, int c);
+/* ustrsizez:
+ *  Returns the size of the specified string in bytes, including the
+ *  trailing zero.
+ */
+extern int ustrsizez(const char *s);
+
 } // namespace AGS3
 
 #endif


Commit: 656659d488a06a34d3756204ead6ed3c9d9f48de
    https://github.com/scummvm/scummvm/commit/656659d488a06a34d3756204ead6ed3c9d9f48de
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:36-07:00

Commit Message:
AGS: Unicode-aware script String API implementation

>From upstream f658f6316dd562b28e8b206f3ef3cedebf3626c0

Changed paths:
    engines/ags/engine/ac/string.cpp


diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index a820b80bb7..b8f62a3d1b 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -20,7 +20,7 @@
  *
  */
 
-//include <cstdio>
+#include "ags/lib/std/algorithm.h"
 #include "ags/engine/ac/string.h"
 #include "ags/shared/ac/common.h"
 #include "ags/engine/ac/display.h"
@@ -66,38 +66,51 @@ const char *String_AppendChar(const char *thisString, char extraOne) {
 }
 
 const char *String_ReplaceCharAt(const char *thisString, int index, char newChar) {
-	if ((index < 0) || (index >= (int)strlen(thisString)))
+	size_t len = ustrlen(thisString);
+	if ((index < 0) || ((size_t)index >= len))
 		quit("!String.ReplaceCharAt: index outside range of string");
 
-	char *buffer = (char *)malloc(strlen(thisString) + 1);
-	strcpy(buffer, thisString);
-	buffer[index] = newChar;
+	size_t off = uoffset(thisString, index);
+	int uchar = ugetc(thisString + off);
+	size_t remain_sz = strlen(thisString + off);
+	size_t old_sz = ucwidth(uchar);
+	size_t new_sz = sizeof(char); // TODO: support unicode char in API
+	size_t total_sz = off + remain_sz + new_sz - old_sz + 1;
+	char *buffer = (char *)malloc(total_sz);
+	memcpy(buffer, thisString, off);
+	usetc(buffer + off, newChar);
+	memcpy(buffer + off + new_sz, thisString + off + old_sz, remain_sz - old_sz + 1);
 	return CreateNewScriptString(buffer, false);
 }
 
 const char *String_Truncate(const char *thisString, int length) {
 	if (length < 0)
 		quit("!String.Truncate: invalid length");
-
-	if (length >= (int)strlen(thisString)) {
+	size_t strlen = ustrlen(thisString);
+	if ((size_t)length >= strlen)
 		return thisString;
-	}
 
-	char *buffer = (char *)malloc(length + 1);
-	strncpy(buffer, thisString, length);
-	buffer[length] = 0;
+	size_t sz = uoffset(thisString, length);
+	char *buffer = (char *)malloc(sz + 1);
+	memcpy(buffer, thisString, sz);
+	buffer[sz] = 0;
 	return CreateNewScriptString(buffer, false);
 }
 
 const char *String_Substring(const char *thisString, int index, int length) {
 	if (length < 0)
 		quit("!String.Substring: invalid length");
-	if ((index < 0) || (index > (int)strlen(thisString)))
+	size_t strlen = ustrlen(thisString);
+	if ((index < 0) || ((size_t)index > strlen))
 		quit("!String.Substring: invalid index");
-
-	char *buffer = (char *)malloc(length + 1);
-	strncpy(buffer, &thisString[index], length);
-	buffer[length] = 0;
+	size_t sublen = std::min((size_t)length, strlen - index);
+	size_t start = uoffset(thisString, index);
+	size_t end = uoffset(thisString + start, sublen) + start;
+	size_t copysz = end - start;
+
+	char *buffer = (char *)malloc(copysz + 1);
+	memcpy(buffer, thisString + start, copysz);
+	buffer[copysz] = 0;
 	return CreateNewScriptString(buffer, false);
 }
 
@@ -106,7 +119,7 @@ int String_CompareTo(const char *thisString, const char *otherString, bool caseS
 	if (caseSensitive) {
 		return strcmp(thisString, otherString);
 	} else {
-		return ags_stricmp(thisString, otherString);
+		return ustricmp(thisString, otherString);
 	}
 }
 
@@ -115,63 +128,69 @@ int String_StartsWith(const char *thisString, const char *checkForString, bool c
 	if (caseSensitive) {
 		return (strncmp(thisString, checkForString, strlen(checkForString)) == 0) ? 1 : 0;
 	} else {
-		return (ags_strnicmp(thisString, checkForString, strlen(checkForString)) == 0) ? 1 : 0;
+		return (ustrnicmp(thisString, checkForString, ustrlen(checkForString)) == 0) ? 1 : 0;
 	}
 }
 
 int String_EndsWith(const char *thisString, const char *checkForString, bool caseSensitive) {
-
-	int checkAtOffset = strlen(thisString) - strlen(checkForString);
-
-	if (checkAtOffset < 0) {
+	// NOTE: we need size in bytes here
+	size_t thislen = strlen(thisString), checklen = strlen(checkForString);
+	if (checklen > thislen)
 		return 0;
-	}
 
 	if (caseSensitive) {
-		return (strcmp(&thisString[checkAtOffset], checkForString) == 0) ? 1 : 0;
+		return (strcmp(thisString + (thislen - checklen), checkForString) == 0) ? 1 : 0;
 	} else {
-		return (ags_stricmp(&thisString[checkAtOffset], checkForString) == 0) ? 1 : 0;
+		return (ustricmp(thisString + (thislen - checklen), checkForString) == 0) ? 1 : 0;
 	}
 }
 
 const char *String_Replace(const char *thisString, const char *lookForText, const char *replaceWithText, bool caseSensitive) {
 	char resultBuffer[STD_BUFFER_SIZE] = "";
-	int thisStringLen = (int)strlen(thisString);
-	int outputSize = 0;
-	for (int i = 0; i < thisStringLen; i++) {
-		bool matchHere = false;
-		if (caseSensitive) {
-			matchHere = (strncmp(&thisString[i], lookForText, strlen(lookForText)) == 0);
-		} else {
-			matchHere = (ags_strnicmp(&thisString[i], lookForText, strlen(lookForText)) == 0);
+	size_t outputSize = 0; // length in bytes
+	if (caseSensitive) {
+		size_t lookForLen = strlen(lookForText);
+		size_t replaceLen = strlen(replaceWithText);
+		for (const char *ptr = thisString; *ptr; ++ptr) {
+			if (strncmp(ptr, lookForText, lookForLen) == 0) {
+				memcpy(&resultBuffer[outputSize], replaceWithText, replaceLen);
+				outputSize += replaceLen;
+				ptr += lookForLen - 1;
+			} else {
+				resultBuffer[outputSize] = *ptr;
+				outputSize++;
+			}
 		}
-
-		if (matchHere) {
-			strcpy(&resultBuffer[outputSize], replaceWithText);
-			outputSize += strlen(replaceWithText);
-			i += strlen(lookForText) - 1;
-		} else {
-			resultBuffer[outputSize] = thisString[i];
-			outputSize++;
+	} else {
+		size_t lookForLen = ustrlen(lookForText);
+		size_t lookForSz = strlen(lookForText); // length in bytes
+		size_t replaceSz = strlen(replaceWithText); // length in bytes
+		const char *p_cur = thisString;
+		for (int c = ugetxc(&thisString); *p_cur; p_cur = thisString, c = ugetxc(&thisString)) {
+			if (ustrnicmp(p_cur, lookForText, lookForLen) == 0) {
+				memcpy(&resultBuffer[outputSize], replaceWithText, replaceSz);
+				outputSize += replaceSz;
+				thisString = p_cur + lookForSz;
+			} else {
+				usetc(&resultBuffer[outputSize], c);
+				outputSize += ucwidth(c);
+			}
 		}
 	}
 
-	resultBuffer[outputSize] = 0;
-
+	resultBuffer[outputSize] = 0; // terminate
 	return CreateNewScriptString(resultBuffer, true);
 }
 
 const char *String_LowerCase(const char *thisString) {
-	char *buffer = (char *)malloc(strlen(thisString) + 1);
-	strcpy(buffer, thisString);
-	ags_strlwr(buffer);
+	char *buffer = ags_strdup(thisString);
+	ustrlwr(buffer);
 	return CreateNewScriptString(buffer, false);
 }
 
 const char *String_UpperCase(const char *thisString) {
-	char *buffer = (char *)malloc(strlen(thisString) + 1);
-	strcpy(buffer, thisString);
-	ags_strupr(buffer);
+	char *buffer = ags_strdup(thisString);
+	ustrupr(buffer);
 	return CreateNewScriptString(buffer, false);
 }
 
@@ -188,36 +207,47 @@ int StringToInt(const char *stino) {
 int StrContains(const char *s1, const char *s2) {
 	VALIDATE_STRING(s1);
 	VALIDATE_STRING(s2);
-	char *tempbuf1 = (char *)malloc(strlen(s1) + 1);
-	char *tempbuf2 = (char *)malloc(strlen(s2) + 1);
-	strcpy(tempbuf1, s1);
-	strcpy(tempbuf2, s2);
-	ags_strlwr(tempbuf1);
-	ags_strlwr(tempbuf2);
-
-	char *offs = strstr(tempbuf1, tempbuf2);
-	free(tempbuf1);
-	free(tempbuf2);
+	char *tempbuf1 = ags_strdup(s1);
+	char *tempbuf2 = ags_strdup(s2);
+	ustrlwr(tempbuf1);
+	ustrlwr(tempbuf2);
 
-	if (offs == nullptr)
+	char *offs = ustrstr(tempbuf1, tempbuf2);
+
+	if (offs == nullptr) {
+		free(tempbuf1);
+		free(tempbuf2);
 		return -1;
+	}
 
-	return (offs - tempbuf1);
+	*offs = 0;
+	int at = ustrlen(tempbuf1);
+	free(tempbuf1);
+	free(tempbuf2);
+	return at;
 }
 
 //=============================================================================
 
+const char *CreateNewScriptString(const String &fromText) {
+	return (const char *)CreateNewScriptStringObj(fromText.GetCStr(), true).second;
+}
+
 const char *CreateNewScriptString(const char *fromText, bool reAllocate) {
 	return (const char *)CreateNewScriptStringObj(fromText, reAllocate).second;
 }
 
+DynObjectRef CreateNewScriptStringObj(const String &fromText) {
+	return CreateNewScriptStringObj(fromText.GetCStr(), true);
+}
+
 DynObjectRef CreateNewScriptStringObj(const char *fromText, bool reAllocate) {
 	ScriptString *str;
 	if (reAllocate) {
 		str = new ScriptString(fromText);
 	} else {
 		str = new ScriptString();
-		str->text = const_cast<char *>(fromText);
+		str->text = (char *)fromText;
 	}
 	void *obj_ptr = str->text;
 	int32_t handle = ccRegisterManagedObject(obj_ptr, str);
@@ -252,29 +282,27 @@ size_t break_up_text_into_lines(const char *todis, SplitLines &lines, int wii, i
 	// write it as normal
 	if (_GP(game).options[OPT_RIGHTLEFTWRITE])
 		for (size_t rr = 0; rr < lines.Count(); rr++) {
-			if (get_uformat() == U_UTF8)
-				lines[rr].ReverseUTF8();
-			else
+			(get_uformat() == U_UTF8) ?
+				lines[rr].ReverseUTF8() :
 				lines[rr].Reverse();
 			line_length = wgettextwidth_compensate(lines[rr].GetCStr(), fonnt);
 			if (line_length > _G(longestline))
 				_G(longestline) = line_length;
-		}
-	else
-		for (size_t rr = 0; rr < lines.Count(); rr++) {
-			line_length = wgettextwidth_compensate(lines[rr].GetCStr(), fonnt);
-			if (line_length > _G(longestline))
-				_G(longestline) = line_length;
-		}
-	return lines.Count();
+		} else
+			for (size_t rr = 0; rr < lines.Count(); rr++) {
+				line_length = wgettextwidth_compensate(lines[rr].GetCStr(), fonnt);
+				if (line_length > _G(longestline))
+					_G(longestline) = line_length;
+			}
+		return lines.Count();
 }
 
 int MAXSTRLEN = MAX_MAXSTRLEN;
 void check_strlen(char *ptt) {
 	MAXSTRLEN = MAX_MAXSTRLEN;
-	uintptr charstart = (uintptr)&_GP(game).chars[0];
-	uintptr charend = charstart + sizeof(CharacterInfo) * _GP(game).numcharacters;
-	if (((uintptr)&ptt[0] >= charstart) && ((uintptr)&ptt[0] <= charend))
+	long charstart = (long)&_GP(game).chars[0];
+	long charend = charstart + sizeof(CharacterInfo) * _GP(game).numcharacters;
+	if (((long)&ptt[0] >= charstart) && ((long)&ptt[0] <= charend))
 		MAXSTRLEN = 30;
 }
 


Commit: 73cd6b9215c3b1199ce4060da7893c257b1491cf
    https://github.com/scummvm/scummvm/commit/73cd6b9215c3b1199ce4060da7893c257b1491cf
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:36-07:00

Commit Message:
AGS: Structure for passing events UTF8 text around along with the keycode

>From upstream 9d65a68c61d0503e05e2cbe80447af0d619161e4

Note that ScummVM doesn't have a TEXT_INPUT event, so this change and following
ones will be mostly to keep in sync with the standalone codebase even if they're
not directly used

Changed paths:
    engines/ags/engine/ac/dialog.cpp
    engines/ags/engine/ac/display.cpp
    engines/ags/engine/ac/inv_window.cpp
    engines/ags/engine/ac/sys_events.cpp
    engines/ags/engine/ac/sys_events.h
    engines/ags/engine/gui/csci_dialog.cpp
    engines/ags/engine/main/game_run.cpp
    engines/ags/engine/main/game_run.h
    engines/ags/engine/media/video/video.cpp
    engines/ags/events.cpp
    engines/ags/events.h
    engines/ags/plugins/ags_plugin.cpp
    engines/ags/shared/ac/keycode.h
    engines/ags/shared/gui/gui_object.h
    engines/ags/shared/gui/gui_textbox.cpp
    engines/ags/shared/gui/gui_textbox.h


diff --git a/engines/ags/engine/ac/dialog.cpp b/engines/ags/engine/ac/dialog.cpp
index c3b5a4b3c6..d612c56560 100644
--- a/engines/ags/engine/ac/dialog.cpp
+++ b/engines/ags/engine/ac/dialog.cpp
@@ -816,21 +816,26 @@ bool DialogOptions::Run() {
 		run_function_on_non_blocking_thread(&_GP(runDialogOptionRepExecFunc));
 	}
 
-	int gkey;
-	if (run_service_key_controls(gkey) && !_GP(play).IsIgnoringInput()) {
+	KeyInput ki;
+	if (run_service_key_controls(ki) && !_GP(play).IsIgnoringInput()) {
+		eAGSKeyCode gkey = ki.Key;
 		if (parserInput) {
 			wantRefresh = true;
-			// type into the parser
+			// type into the parser 
+			// TODO: find out what are these key commands, and are these documented?
 			if ((gkey == eAGSKeyCodeF3) || ((gkey == eAGSKeyCodeSpace) && (parserInput->Text.GetLength() == 0))) {
 				// write previous contents into textbox (F3 or Space when box is empty)
-				for (unsigned int i = parserInput->Text.GetLength(); i < strlen(_GP(play).lastParserEntry); i++) {
-					parserInput->OnKeyPress(_GP(play).lastParserEntry[i]);
+				size_t last_len = strlen(_GP(play).lastParserEntry);
+
+				for (unsigned int i = parserInput->Text.GetLength(); i < last_len; i++) {
+					ki.Key = (eAGSKeyCode)_GP(play).lastParserEntry[i];
+					parserInput->OnKeyPress(ki);
 				}
 				//ags_domouse(DOMOUSE_DISABLE);
 				Redraw();
 				return true; // continue running loop
 			} else if ((gkey >= eAGSKeyCodeSpace) || (gkey == eAGSKeyCodeReturn) || (gkey == eAGSKeyCodeBackspace)) {
-				parserInput->OnKeyPress(gkey);
+				parserInput->OnKeyPress(ki);
 				if (!parserInput->IsActivated) {
 					//ags_domouse(DOMOUSE_DISABLE);
 					Redraw();
@@ -844,10 +849,10 @@ bool DialogOptions::Run() {
 		}
 		// Allow selection of options by keyboard shortcuts
 		else if (_GP(game).options[OPT_DIALOGNUMBERED] >= kDlgOptKeysOnly &&
-		         gkey >= '1' && gkey <= '9') {
-			gkey -= '1';
-			if (gkey < numdisp) {
-				chose = disporder[gkey];
+			gkey >= '1' && gkey <= '9') {
+			int numkey = gkey - '1';
+			if (numkey < numdisp) {
+				chose = disporder[numkey];
 				return false; // end dialog options running loop
 			}
 		}
@@ -857,8 +862,8 @@ bool DialogOptions::Run() {
 	if (new_custom_render); // do not automatically detect option under mouse
 	else if (usingCustomRendering) {
 		if ((_G(mousex) >= dirtyx) && (_G(mousey) >= dirtyy) &&
-		        (_G(mousex) < dirtyx + tempScrn->GetWidth()) &&
-		        (_G(mousey) < dirtyy + tempScrn->GetHeight())) {
+			(_G(mousex) < dirtyx + tempScrn->GetWidth()) &&
+			(_G(mousey) < dirtyy + tempScrn->GetHeight())) {
 			_GP(getDialogOptionUnderCursorFunc).params[0].SetDynamicObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
 			run_function_on_non_blocking_thread(&_GP(getDialogOptionUnderCursorFunc));
 
@@ -870,24 +875,23 @@ bool DialogOptions::Run() {
 			_GP(ccDialogOptionsRendering).activeOptionID = -1;
 		}
 	} else if (_G(mousex) >= dialog_abs_x && _G(mousex) < (dialog_abs_x + areawid) &&
-	           _G(mousey) >= dlgyp && _G(mousey) < curyp) {
+		_G(mousey) >= dlgyp && _G(mousey) < curyp) {
 		mouseison = numdisp - 1;
 		for (int i = 0; i < numdisp; ++i) {
 			if (_G(mousey) < dispyp[i]) {
-				mouseison = i - 1;
-				break;
+				mouseison = i - 1; break;
 			}
 		}
 		if ((mouseison < 0) | (mouseison >= numdisp)) mouseison = -1;
 	}
 
 	if (parserInput != nullptr) {
-		int relativeMousey = _G(mousey);
+		int relative_mousey = _G(mousey);
 		if (usingCustomRendering)
-			relativeMousey -= dirtyy;
+			relative_mousey -= dirtyy;
 
-		if ((relativeMousey > parserInput->Y) &&
-		        (relativeMousey < parserInput->Y + parserInput->Height))
+		if ((relative_mousey > parserInput->Y) &&
+			(relative_mousey < parserInput->Y + parserInput->Height))
 			mouseison = DLG_OPTION_PARSER;
 
 		if (parserInput->IsActivated)
@@ -897,7 +901,7 @@ bool DialogOptions::Run() {
 	int mouseButtonPressed = MouseNone;
 	int mouseWheelTurn = 0;
 	if (run_service_mb_controls(mouseButtonPressed, mouseWheelTurn) && mouseButtonPressed >= 0 &&
-	        !_GP(play).IsIgnoringInput()) {
+		!_GP(play).IsIgnoringInput()) {
 		if (mouseison < 0 && !new_custom_render) {
 			if (usingCustomRendering) {
 				_GP(runDialogOptionMouseClickHandlerFunc).params[0].SetDynamicObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
@@ -970,8 +974,7 @@ bool DialogOptions::Run() {
 
 	update_polled_stuff_if_runtime();
 
-	if (!runGameLoopsInBackground && (_GP(play).fast_forward == 0)) {
-		// note if runGameLoopsInBackground then it's called inside UpdateGameOnce
+	if (!runGameLoopsInBackground && (_GP(play).fast_forward == 0)) { // note if runGameLoopsInBackground then it's called inside UpdateGameOnce
 		WaitForNextFrame();
 	}
 
diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index 19928d4fcb..59671a472c 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -289,9 +289,9 @@ int _display_main(int xx, int yy, int wii, const char *text, int disp_type, int
 				if (skip_setting & SKIP_MOUSECLICK && !_GP(play).IsIgnoringInput())
 					break;
 			}
-			int kp;
+			KeyInput kp;
 			if (run_service_key_controls(kp)) {
-				check_skip_cutscene_keypress(kp);
+				check_skip_cutscene_keypress(kp.Key);
 				if (_GP(play).fast_forward)
 					break;
 				if ((skip_setting & SKIP_KEYPRESS) && !_GP(play).IsIgnoringInput())
diff --git a/engines/ags/engine/ac/inv_window.cpp b/engines/ags/engine/ac/inv_window.cpp
index 47282bd40b..8794f8ab49 100644
--- a/engines/ags/engine/ac/inv_window.cpp
+++ b/engines/ags/engine/ac/inv_window.cpp
@@ -347,7 +347,7 @@ bool InventoryScreen::Run() {
 	// Run() can be called in a loop, so keep events going.
 	sys_evt_process_pending();
 
-	int kgn;
+	KeyInput kgn;
 	if (run_service_key_controls(kgn) && !_GP(play).IsIgnoringInput()) {
 		return false; // end inventory screen loop
 	}
diff --git a/engines/ags/engine/ac/sys_events.cpp b/engines/ags/engine/ac/sys_events.cpp
index 342067aff9..6e6347b68d 100644
--- a/engines/ags/engine/ac/sys_events.cpp
+++ b/engines/ags/engine/ac/sys_events.cpp
@@ -64,6 +64,14 @@ static void(*_on_switchout_callback)(void) = nullptr;
 // KEYBOARD INPUT
 // ----------------------------------------------------------------------------
 
+KeyInput ags_keycode_from_scummvm(const Common::Event &event) {
+	KeyInput ki;
+
+	ki.Key = ::AGS::g_events->scummvm_key_to_ags_key(event);
+
+	return ki;
+}
+
 bool ags_keyevent_ready() {
 	return ::AGS::g_events->keyEventPending();
 }
diff --git a/engines/ags/engine/ac/sys_events.h b/engines/ags/engine/ac/sys_events.h
index e11ae305ad..bd60202726 100644
--- a/engines/ags/engine/ac/sys_events.h
+++ b/engines/ags/engine/ac/sys_events.h
@@ -66,8 +66,7 @@ inline int make_merged_mod(int mod) {
 	return m_mod;
 }
 
-extern eAGSKeyCode ags_keycode_from_ScummVM(const Common::Event &event);
-extern bool ags_key_to_scummvm_keycode(eAGSKeyCode key, Common::KeyCode(&scan)[3]);
+extern KeyInput ags_keycode_from_scummvm(const Common::Event &event);
 
 // Tells if there are any buffered key events
 extern bool ags_keyevent_ready();
diff --git a/engines/ags/engine/gui/csci_dialog.cpp b/engines/ags/engine/gui/csci_dialog.cpp
index 4d8b7dcf0a..055353b907 100644
--- a/engines/ags/engine/gui/csci_dialog.cpp
+++ b/engines/ags/engine/gui/csci_dialog.cpp
@@ -135,8 +135,9 @@ int CSCIWaitMessage(CSCIMessage *cscim) {
 		cscim->id = -1;
 		cscim->code = 0;
 		_G(smcode) = 0;
-		int keywas;
-		if (run_service_key_controls(keywas) && !_GP(play).IsIgnoringInput()) {
+		KeyInput ki;
+		if (run_service_key_controls(ki) && !_GP(play).IsIgnoringInput()) {
+			int keywas = ki.Key;
 			if (keywas == eAGSKeyCodeReturn) {
 				cscim->id = finddefaultcontrol(CNF_DEFAULT);
 				cscim->code = CM_COMMAND;
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index 0563de19f5..c0af5bd89b 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -245,7 +245,7 @@ int old_key_mod = 0; // for saving previous key mods
 
 // Runs service key controls, returns false if service key combinations were handled
 // and no more processing required, otherwise returns true and provides current keycode and key shifts.
-bool run_service_key_controls(int &out_key) {
+bool run_service_key_controls(KeyInput &out_key) {
 	bool handled = false;
 	const bool key_valid = ags_keyevent_ready();
 	const Common::Event key_evt = key_valid ? ags_get_next_keyevent() : Common::Event();
@@ -305,7 +305,8 @@ bool run_service_key_controls(int &out_key) {
 		return false; // rest of engine currently does not use pressed mod keys
 	// change this when it's no longer true (but be mindful about key-skipping!)
 
-	int agskey = ::AGS::EventsManager::ags_keycode_from_scummvm(key_evt);
+	KeyInput ki = ags_keycode_from_scummvm(key_evt);
+	eAGSKeyCode agskey = ki.Key;
 	if (agskey == eAGSKeyCodeNone)
 		return false; // should skip this key event
 
@@ -390,7 +391,7 @@ bool run_service_key_controls(int &out_key) {
 	}
 
 	// No service operation triggered? return active keypress and mods to caller
-	out_key = agskey;
+	out_key = ki;
 	return true;
 }
 
@@ -408,10 +409,11 @@ bool run_service_mb_controls(int &mbut, int &mwheelz) {
 // Runs default keyboard handling
 static void check_keyboard_controls() {
 	// First check for service engine's combinations (mouse lock, display mode switch, and so forth)
-	int kgn;
-	if (!run_service_key_controls(kgn)) {
+	KeyInput ki;
+	if (!run_service_key_controls(ki)) {
 		return;
 	}
+	eAGSKeyCode kgn = ki.Key;
 	// Then, check cutscene skip
 	check_skip_cutscene_keypress(kgn);
 	if (_GP(play).fast_forward) {
@@ -487,7 +489,7 @@ static void check_keyboard_controls() {
 
 				keywasprocessed = 1;
 
-				guitex->OnKeyPress(kgn);
+				guitex->OnKeyPress(ki);
 
 				if (guitex->IsActivated) {
 					guitex->IsActivated = false;
@@ -498,9 +500,9 @@ static void check_keyboard_controls() {
 	}
 
 	if (!keywasprocessed) {
-		kgn = AGSKeyToScriptKey(kgn);
-		debug_script_log("Running on_key_press keycode %d", kgn);
-		setevent(EV_TEXTSCRIPT, TS_KEYPRESS, kgn);
+		int sckey = AGSKeyToScriptKey(kgn);
+		debug_script_log("Running on_key_press keycode %d", sckey);
+		setevent(EV_TEXTSCRIPT, TS_KEYPRESS, sckey);
 	}
 
 	// RunTextScriptIParam(_G(gameinst),"on_key_press",kgn);
diff --git a/engines/ags/engine/main/game_run.h b/engines/ags/engine/main/game_run.h
index 87527e173c..762492e2a5 100644
--- a/engines/ags/engine/main/game_run.h
+++ b/engines/ags/engine/main/game_run.h
@@ -51,7 +51,8 @@ void UpdateGameOnce(bool checkControls = false, IDriverDependantBitmap *extraBit
 float get_current_fps();
 // Runs service key controls, returns false if no key was pressed or key input was claimed by the engine,
 // otherwise returns true and provides a keycode.
-bool run_service_key_controls(int &kgn);
+struct KeyInput;
+bool run_service_key_controls(KeyInput &kgn);
 // Runs service mouse controls, returns false if mouse input was claimed by the engine,
 // otherwise returns true and provides mouse button code.
 bool run_service_mb_controls(int &mbut, int &mwheelz);
diff --git a/engines/ags/engine/media/video/video.cpp b/engines/ags/engine/media/video/video.cpp
index a17c18c0e4..bdf4096d9b 100644
--- a/engines/ags/engine/media/video/video.cpp
+++ b/engines/ags/engine/media/video/video.cpp
@@ -112,9 +112,10 @@ static bool play_video(Video::VideoDecoder *decoder, const char *name, int skip,
 
 		if (canAbort) {
 			// Check for whether user aborted video
-			int key, mbut, mwheelz;
+			KeyInput key;
+			int mbut, mwheelz;
 			if (run_service_key_controls(key)) {
-				if (key == 27 && canAbort)
+				if (key.Key == 27 && canAbort)
 					return true;
 				if (canAbort >= 2)
 					return true;  // skip on any key
diff --git a/engines/ags/events.cpp b/engines/ags/events.cpp
index f6ec16326f..4928556ba0 100644
--- a/engines/ags/events.cpp
+++ b/engines/ags/events.cpp
@@ -311,7 +311,7 @@ bool EventsManager::ags_key_to_scancode(AGS3::eAGSKeyCode key, Common::KeyCode(&
 	return false;
 }
 
-AGS3::eAGSKeyCode EventsManager::ags_keycode_from_scummvm(const Common::Event &event) {
+AGS3::eAGSKeyCode EventsManager::scummvm_key_to_ags_key(const Common::Event &event) {
 	if (event.type != Common::EVENT_KEYDOWN)
 		return AGS3::eAGSKeyCodeNone;
 
diff --git a/engines/ags/events.h b/engines/ags/events.h
index 5a69c66896..46db89ed85 100644
--- a/engines/ags/events.h
+++ b/engines/ags/events.h
@@ -53,7 +53,7 @@ public:
 	/*
 	 * Converts a ScummVM event to the ags keycode
 	 */
-	static AGS3::eAGSKeyCode ags_keycode_from_scummvm(const Common::Event &event);
+	static AGS3::eAGSKeyCode scummvm_key_to_ags_key(const Common::Event &event);
 
 public:
 	EventsManager();
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index d10e472bbc..818a4308b9 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -369,18 +369,17 @@ void IAGSEngine::BlitSpriteRotated(int32 x, int32 y, BITMAP *bmp, int32 angle) {
 extern void domouse(int);
 
 void IAGSEngine::PollSystem() {
-
 	domouse(DOMOUSE_NOCURSOR);
 	update_polled_stuff_if_runtime();
 	int mbut, mwheelz;
 	if (run_service_mb_controls(mbut, mwheelz) && mbut >= 0 && !_GP(play).IsIgnoringInput())
 		pl_run_plugin_hooks(AGSE_MOUSECLICK, mbut);
-	int kp;
+	KeyInput kp;
 	if (run_service_key_controls(kp) && !_GP(play).IsIgnoringInput()) {
-		pl_run_plugin_hooks(AGSE_KEYPRESS, kp);
+		pl_run_plugin_hooks(AGSE_KEYPRESS, kp.Key);
 	}
-
 }
+
 AGSCharacter *IAGSEngine::GetCharacter(int32 charnum) {
 	if (charnum >= _GP(game).numcharacters)
 		quit("!AGSEngine::GetCharacter: invalid character request");
diff --git a/engines/ags/shared/ac/keycode.h b/engines/ags/shared/ac/keycode.h
index 9903749d48..b541aad747 100644
--- a/engines/ags/shared/ac/keycode.h
+++ b/engines/ags/shared/ac/keycode.h
@@ -24,6 +24,7 @@
 #define AGS_SHARED_AC_KEYCODE_H
 
 #include "ags/shared/core/platform.h"
+#include "ags/shared/core/types.h"
 
 namespace AGS3 {
 
@@ -251,6 +252,15 @@ enum eAGSKeyCode {
 	*/
 };
 
+// Combined key code and a textual representation in UTF-8
+struct KeyInput {
+	const static size_t UTF8_ARR_SIZE = 5;
+
+	eAGSKeyCode Key = eAGSKeyCodeNone;
+	char        Text[UTF8_ARR_SIZE] = { 0 };
+
+	KeyInput() = default;
+};
 
 // Converts eAGSKeyCode to script API code, for "on_key_press" and similar callbacks
 int AGSKeyToScriptKey(int keycode);
diff --git a/engines/ags/shared/gui/gui_object.h b/engines/ags/shared/gui/gui_object.h
index 083bd566b0..db40ccb08c 100644
--- a/engines/ags/shared/gui/gui_object.h
+++ b/engines/ags/shared/gui/gui_object.h
@@ -36,6 +36,8 @@ namespace AGS3 {
 #define GUIDIS_UNCHANGED 4
 #define GUIDIS_GUIOFF  0x80
 
+struct KeyInput;
+
 namespace AGS {
 namespace Shared {
 
@@ -73,8 +75,7 @@ public:
 
 	// Events
 	// Key pressed for control
-	virtual void    OnKeyPress(int keycode) {
-	}
+	virtual void    OnKeyPress(const KeyInput &ki) {}
 	// Mouse button down - return 'True' to lock focus
 	virtual bool    OnMouseDown() {
 		return false;
diff --git a/engines/ags/shared/gui/gui_textbox.cpp b/engines/ags/shared/gui/gui_textbox.cpp
index b2e606d81d..c487f69b85 100644
--- a/engines/ags/shared/gui/gui_textbox.cpp
+++ b/engines/ags/shared/gui/gui_textbox.cpp
@@ -61,7 +61,9 @@ void GUITextBox::Draw(Bitmap *ds) {
 	DrawTextBoxContents(ds, text_color);
 }
 
-void GUITextBox::OnKeyPress(int keycode) {
+void GUITextBox::OnKeyPress(const KeyInput &ki) {
+	eAGSKeyCode keycode = ki.Key;
+
 	// other key, continue
 	if ((keycode >= 128) && (!font_supports_extended_characters(Font)))
 		return;
diff --git a/engines/ags/shared/gui/gui_textbox.h b/engines/ags/shared/gui/gui_textbox.h
index ebd3c457a9..d3275ed6aa 100644
--- a/engines/ags/shared/gui/gui_textbox.h
+++ b/engines/ags/shared/gui/gui_textbox.h
@@ -42,7 +42,7 @@ public:
 	void SetShowBorder(bool on);
 
 	// Events
-	void OnKeyPress(int keycode) override;
+	void OnKeyPress(const KeyInput &ki) override;
 
 	// Serialization
 	void ReadFromFile(Stream *in, GuiVersion gui_version) override;


Commit: 4cb95e324e3bf416ba928078ce3f41db36356529
    https://github.com/scummvm/scummvm/commit/4cb95e324e3bf416ba928078ce3f41db36356529
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:36-07:00

Commit Message:
AGS: TextBox control supports unicode

>From upstream 960e6f54a5b255f21fa9366167b3a8dec75230db

Changed paths:
    engines/ags/shared/gui/gui_textbox.cpp


diff --git a/engines/ags/shared/gui/gui_textbox.cpp b/engines/ags/shared/gui/gui_textbox.cpp
index c487f69b85..242e7b95e4 100644
--- a/engines/ags/shared/gui/gui_textbox.cpp
+++ b/engines/ags/shared/gui/gui_textbox.cpp
@@ -61,6 +61,18 @@ void GUITextBox::Draw(Bitmap *ds) {
 	DrawTextBoxContents(ds, text_color);
 }
 
+// TODO: a shared utility function
+static void Backspace(String &text) {
+	if (get_uformat() == U_UTF8) {// Find where the last utf8 char begins
+		const char *ptr_end = text.GetCStr() + text.GetLength();
+		const char *ptr = ptr_end - 1;
+		for (; ptr > text.GetCStr() && ((*ptr & 0xC0) == 0x80); --ptr);
+		text.ClipRight(ptr_end - ptr);
+	} else {
+		text.ClipRight(1);
+	}
+}
+
 void GUITextBox::OnKeyPress(const KeyInput &ki) {
 	eAGSKeyCode keycode = ki.Key;
 
@@ -71,7 +83,7 @@ void GUITextBox::OnKeyPress(const KeyInput &ki) {
 	NotifyParentChanged();
 	// backspace, remove character
 	if (keycode == eAGSKeyCodeBackspace) {
-		Text.ClipRight(1);
+		Backspace(Text);
 		return;
 	}
 	// return/enter
@@ -83,7 +95,7 @@ void GUITextBox::OnKeyPress(const KeyInput &ki) {
 	Text.AppendChar(keycode);
 	// if the new string is too long, remove the new character
 	if (wgettextwidth(Text.GetCStr(), Font) > (Width - (6 + get_fixed_pixel_size(5))))
-		Text.ClipRight(1);
+		Backspace(Text);
 }
 
 void GUITextBox::SetShowBorder(bool on) {


Commit: 5dec9be2e9864d70f1a137bdaf5ba90f6708cd4e
    https://github.com/scummvm/scummvm/commit/5dec9be2e9864d70f1a137bdaf5ba90f6708cd4e
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:36-07:00

Commit Message:
AGS: Skeleton for converting UTF-8 translation keys to ASCII

>From upstream e04f86a91b248f598fccf04369e0874393a9a45d

Upstream uses locale, but since that isn't available to a
ScummVM engine, for now it's just a skeleton implementation

Changed paths:
    engines/ags/engine/ac/translation.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/translation.cpp b/engines/ags/engine/ac/translation.cpp
index e91e250d07..7cd27a2bc8 100644
--- a/engines/ags/engine/ac/translation.cpp
+++ b/engines/ags/engine/ac/translation.cpp
@@ -20,6 +20,7 @@
  *
  */
 
+#include "common/language.h"
 #include "ags/engine/ac/asset_helper.h"
 #include "ags/shared/ac/common.h"
 #include "ags/engine/ac/game_setup.h"
@@ -40,6 +41,13 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
+// TODO: Since ScummVM can't use uconvert, this is a dummy implementation for now
+const char *convert_utf8_to_ascii(const char *mbstr, const char *loc_name) {
+	_G(mbbuf).resize(strlen(mbstr) + 1);
+	strcpy(&_G(mbbuf)[0], mbstr);
+	return &_G(mbbuf)[0];
+}
+
 void close_translation() {
 	_GP(transtree).clear();
 	_GP(trans) = Translation();
@@ -111,6 +119,22 @@ bool init_translation(const String &lang, const String &fallback_lang, bool quit
 	else
 		set_uformat(U_ASCII);
 
+	// Mixed encoding support: 
+	// original text unfortunately may contain extended ASCII chars (> 127);
+	// if translation is UTF-8 but game is extended ASCII, then the translation
+	// dictionary keys won't match.
+	// With that assumption we must convert dictionary keys into ASCII using
+	// provided locale hint.
+	String key_enc = _GP(trans).StrOptions["gameencoding"];
+	if (!key_enc.IsEmpty()) {
+		StringMap conv_map;
+		for (const auto &item : _GP(trans).Dict) {
+			String key = convert_utf8_to_ascii(item._key.GetCStr(), key_enc.GetCStr());
+			conv_map.insert(std::make_pair(key, item._value));
+		}
+		_GP(trans).Dict = conv_map;
+	}
+
 	Debug::Printf("Translation initialized: %s", _G(trans_filename).GetCStr());
 	return true;
 }
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 0f9d6f6dc7..12e07cd071 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1319,6 +1319,8 @@ public:
 	String _trans_name, _trans_filename;
 	long _lang_offs_start = 0;
 	char _transFileName[MAX_PATH] = { 0 };
+	std::vector<uint16> _wcsbuf; // widechar buffer
+	std::vector<char> _mbbuf;  // utf8 buffer
 
 	/**@}*/
 


Commit: adc208ca4f8c79ffec9bea1971d6e452533e0f49
    https://github.com/scummvm/scummvm/commit/adc208ca4f8c79ffec9bea1971d6e452533e0f49
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:37-07:00

Commit Message:
AGS: text parser's F3 command works with unicode

>From upstream d47ddd66dee768fd76fea74f888929bb9c748323

Changed paths:
    engines/ags/engine/ac/dialog.cpp


diff --git a/engines/ags/engine/ac/dialog.cpp b/engines/ags/engine/ac/dialog.cpp
index d612c56560..9673eb633d 100644
--- a/engines/ags/engine/ac/dialog.cpp
+++ b/engines/ags/engine/ac/dialog.cpp
@@ -825,12 +825,17 @@ bool DialogOptions::Run() {
 			// TODO: find out what are these key commands, and are these documented?
 			if ((gkey == eAGSKeyCodeF3) || ((gkey == eAGSKeyCodeSpace) && (parserInput->Text.GetLength() == 0))) {
 				// write previous contents into textbox (F3 or Space when box is empty)
-				size_t last_len = strlen(_GP(play).lastParserEntry);
-
-				for (unsigned int i = parserInput->Text.GetLength(); i < last_len; i++) {
-					ki.Key = (eAGSKeyCode)_GP(play).lastParserEntry[i];
-					parserInput->OnKeyPress(ki);
+				size_t last_len = ustrlen(_GP(play).lastParserEntry);
+				size_t cur_len = ustrlen(parserInput->Text.GetCStr());
+				// [ikm] CHECKME: tbh I don't quite get the logic here (it was like this in original code);
+				// but what we do is copying only the last part of the previous string
+				if (cur_len < last_len) {
+					const char *entry = _GP(play).lastParserEntry;
+					// TODO: utility function for advancing N utf-8 chars
+					for (size_t i = 0; i < cur_len; ++i) ugetxc(&entry);
+					parserInput->Text.Append(entry);
 				}
+
 				//ags_domouse(DOMOUSE_DISABLE);
 				Redraw();
 				return true; // continue running loop


Commit: 188951a4998d18a95d1f4f835669449151d16bd7
    https://github.com/scummvm/scummvm/commit/188951a4998d18a95d1f4f835669449151d16bd7
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:38-07:00

Commit Message:
AGS: Supports reading scripts as separate assets

>From upstream 4de348c79b14dc9f6801b497ae5d7ea4fa1077cd

Changed paths:
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/game/game_init.h
    engines/ags/engine/main/game_file.cpp
    engines/ags/shared/game/main_game_file.cpp
    engines/ags/shared/game/main_game_file.h


diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 84f348f269..ca09ed8869 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -75,6 +75,8 @@ String GetGameInitErrorText(GameInitErrorType err) {
 		return "Too many plugins for this engine to handle.";
 	case kGameInitErr_PluginNameInvalid:
 		return "Plugin name is invalid.";
+	case kGameInitErr_NoGlobalScript:
+		return "No global script in game.";
 	case kGameInitErr_ScriptLinkFailed:
 		return "Script link failed.";
 	}
@@ -366,6 +368,8 @@ HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion dat
 	// NOTE: we must do this after plugins, because some plugins may export
 	// script symbols too.
 	//
+	if (!ents.GlobalScript)
+		return new GameInitError(kGameInitErr_NoGlobalScript);
 	_GP(gamescript) = ents.GlobalScript;
 	_GP(dialogScriptsScript) = ents.DialogScript;
 	_G(numScriptModules) = ents.ScriptModules.size();
diff --git a/engines/ags/engine/game/game_init.h b/engines/ags/engine/game/game_init.h
index fb9550aa94..31b7d6fc03 100644
--- a/engines/ags/engine/game/game_init.h
+++ b/engines/ags/engine/game/game_init.h
@@ -48,6 +48,7 @@ enum GameInitErrorType {
 	kGameInitErr_EntityInitFail,
 	kGameInitErr_TooManyPlugins,
 	kGameInitErr_PluginNameInvalid,
+	kGameInitErr_NoGlobalScript,
 	kGameInitErr_ScriptLinkFailed
 };
 
diff --git a/engines/ags/engine/main/game_file.cpp b/engines/ags/engine/main/game_file.cpp
index 373c8b13d1..3ad68abbba 100644
--- a/engines/ags/engine/main/game_file.cpp
+++ b/engines/ags/engine/main/game_file.cpp
@@ -47,6 +47,7 @@
 #include "ags/engine/gfx/blender.h"
 #include "ags/shared/core/asset_manager.h"
 #include "ags/shared/util/aligned_stream.h"
+#include "ags/shared/util/text_stream_reader.h"
 #include "ags/engine/ac/game_setup.h"
 #include "ags/shared/game/main_game_file.h"
 #include "ags/engine/game/game_init.h"
@@ -117,13 +118,66 @@ HError preload_game_data() {
 	return HError::None();
 }
 
+static inline HError MakeScriptLoadError(const char *name) {
+	return new Error(String::FromFormat(
+		"Failed to load a script module: %s", name),
+		_G(ccErrorString));
+}
+
+// Looks up for the game scripts available as separate assets.
+// These are optional, so no error is raised if some of these are not found.
+// For those that do exist, reads them and replaces any scripts of same kind
+// in the already loaded game data.
+HError LoadGameScripts(LoadedGameEntities &ents, GameDataVersion data_ver) {
+	// Global script 
+	std::unique_ptr<Stream> in(_GP(AssetMgr)->OpenAsset("GlobalScript.o"));
+	if (in) {
+		PScript script(ccScript::CreateFromStream(in.get()));
+		if (!script)
+			return MakeScriptLoadError("GlobalScript.o");
+		ents.GlobalScript = script;
+	}
+	// Dialog script
+	in.reset(_GP(AssetMgr)->OpenAsset("DialogScript.o"));
+	if (in) {
+		PScript script(ccScript::CreateFromStream(in.get()));
+		if (!script)
+			return MakeScriptLoadError("DialogScript.o");
+		ents.DialogScript = script;
+	}
+	// Script modules
+	// First load a modules list
+	std::vector<String> modules;
+	in.reset(_GP(AssetMgr)->OpenAsset("ScriptModules.lst"));
+	if (in) {
+		TextStreamReader reader(in.get());
+		in.release(); // TextStreamReader got it
+		while (!reader.EOS())
+			modules.push_back(reader.ReadLine());
+	}
+	if (modules.size() > ents.ScriptModules.size())
+		ents.ScriptModules.resize(modules.size());
+	// Now run by the list and try loading everything
+	for (size_t i = 0; i < modules.size(); ++i) {
+		in.reset(_GP(AssetMgr)->OpenAsset(modules[i]));
+		if (in) {
+			PScript script(ccScript::CreateFromStream(in.get()));
+			if (!script)
+				return MakeScriptLoadError(modules[i].GetCStr());
+			ents.ScriptModules[i] = script;
+		}
+	}
+	return HError::None();
+}
+
 HError load_game_file() {
 	MainGameSource src;
 	LoadedGameEntities ents(_GP(game), _G(dialog), _G(views));
-	HGameFileError load_err = OpenMainGameFileFromDefaultAsset(src);
-	if (load_err) {
-		load_err = ReadGameData(ents, src.InputStream.get(), src.DataVersion);
-		if (load_err) {
+	HError err = (HError)OpenMainGameFileFromDefaultAsset(src);
+	if (err) {
+		err = (HError)ReadGameData(ents, src.InputStream.get(), src.DataVersion);
+		src.InputStream.reset();
+		if (err) {
 			// Upscale mode -- for old games that supported it.
 			// NOTE: this must be done before UpdateGameData, or resolution-dependant
 			// adjustments won't be applied correctly.
@@ -134,14 +188,18 @@ HError load_game_file() {
 					_GP(game).SetGameResolution(kGameResolution_640x480);
 			}
 
-			load_err = UpdateGameData(ents, src.DataVersion);
+			err = (HError)UpdateGameData(ents, src.DataVersion);
 		}
 	}
-	if (!load_err)
-		return (HError)load_err;
-	HGameInitError init_err = InitGameState(ents, src.DataVersion);
-	if (!init_err)
-		return (HError)init_err;
+
+	if (!err)
+		return err;
+	err = LoadGameScripts(ents, src.DataVersion);
+	if (!err)
+		return err;
+	err = (HError)InitGameState(ents, src.DataVersion);
+	if (!err)
+		return err;
 	return HError::None();
 }
 
diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index ab7db262bd..0b8b153faf 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -76,8 +76,6 @@ String GetMainGameFileErrorText(MainGameFileErrorType err) {
 		return "Failed to deserialize custom properties schema.";
 	case kMGFErr_InvalidPropertyValues:
 		return "Errors encountered when reading custom properties.";
-	case kMGFErr_NoGlobalScript:
-		return "No global script in _GP(game).";
 	case kMGFErr_CreateGlobalScriptFailed:
 		return "Failed to load global script.";
 	case kMGFErr_CreateDialogScriptFailed:
@@ -732,17 +730,17 @@ HGameFileError ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersio
 	_GP(game).read_interaction_scripts(in, data_ver);
 	_GP(game).read_words_dictionary(in);
 
-	if (!_GP(game).load_compiled_script)
-		return new MainGameFileError(kMGFErr_NoGlobalScript);
-	ents.GlobalScript.reset(ccScript::CreateFromStream(in));
-	if (!ents.GlobalScript)
-		return new MainGameFileError(kMGFErr_CreateGlobalScriptFailed, _G(ccErrorString));
-	err = ReadDialogScript(ents.DialogScript, in, data_ver);
-	if (!err)
-		return err;
-	err = ReadScriptModules(ents.ScriptModules, in, data_ver);
-	if (!err)
-		return err;
+	if (game.load_compiled_script) {
+		ents.GlobalScript.reset(ccScript::CreateFromStream(in));
+		if (!ents.GlobalScript)
+			return new MainGameFileError(kMGFErr_CreateGlobalScriptFailed, _G(ccErrorString));
+		err = ReadDialogScript(ents.DialogScript, in, data_ver);
+		if (!err)
+			return err;
+		err = ReadScriptModules(ents.ScriptModules, in, data_ver);
+		if (!err)
+			return err;
+	}
 
 	ReadViews(game, ents.Views, in, data_ver);
 
diff --git a/engines/ags/shared/game/main_game_file.h b/engines/ags/shared/game/main_game_file.h
index 4e92aadb7b..fed671ed9a 100644
--- a/engines/ags/shared/game/main_game_file.h
+++ b/engines/ags/shared/game/main_game_file.h
@@ -67,7 +67,6 @@ enum MainGameFileErrorType {
 	kMGFErr_TooManyCursors,
 	kMGFErr_InvalidPropertySchema,
 	kMGFErr_InvalidPropertyValues,
-	kMGFErr_NoGlobalScript,
 	kMGFErr_CreateGlobalScriptFailed,
 	kMGFErr_CreateDialogScriptFailed,
 	kMGFErr_CreateScriptModuleFailed,


Commit: 02883d6d8958317db0065bd5710381f863845a63
    https://github.com/scummvm/scummvm/commit/02883d6d8958317db0065bd5710381f863845a63
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:38-07:00

Commit Message:
AGS: Supports reading room scripts as separate assets

>From upstream ec3f214e1c857d1c9b76f70c2446e9f229968afb

Changed paths:
    engines/ags/engine/ac/room.cpp


diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 67ab6a5a1a..d5121e718f 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -396,7 +396,25 @@ static void update_all_viewcams_with_newroom() {
 	}
 }
 
-// forchar = _G(playerchar) on NewRoom, or NULL if restore saved game
+// Looks up for the room script available as a separate asset.
+// This is optional, so no error is raised if one is not found.
+// If found however, it will replace room script if one had been loaded
+// from the room file itself.
+HError LoadRoomScript(RoomStruct *room, int newnum) {
+	String filename = String::FromFormat("room%d.o", newnum);
+	std::unique_ptr<Stream> in(_GP(AssetMgr)->OpenAsset(filename));
+	if (in) {
+		PScript script(ccScript::CreateFromStream(in.get()));
+		if (!script)
+			return new Error(String::FromFormat(
+				"Failed to load a script module: %s", filename.GetCStr()),
+				_G(ccErrorString));
+		room->CompiledScript = script;
+	}
+	return HError::None();
+}
+
+// forchar = playerchar on NewRoom, or NULL if restore saved game
 void load_new_room(int newnum, CharacterInfo *forchar) {
 
 	debug_script_log("Loading room %d", newnum);
@@ -434,6 +452,11 @@ void load_new_room(int newnum, CharacterInfo *forchar) {
 		quitprintf("!Unable to load '%s'. This room file is assigned to a different _GP(game).", room_filename.GetCStr());
 	}
 
+	HError err = LoadRoomScript(&_GP(thisroom), newnum);
+	if (!err)
+		quitprintf("!Unable to load '%s'. Error: %s", room_filename.GetCStr(),
+			err->FullMessage().GetCStr());
+
 	convert_room_coordinates_to_data_res(&_GP(thisroom));
 
 	update_polled_stuff_if_runtime();


Commit: ba10eb247e546a8fe49f57f40402e7b6cfeb18ea
    https://github.com/scummvm/scummvm/commit/ba10eb247e546a8fe49f57f40402e7b6cfeb18ea
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:38-07:00

Commit Message:
AGS: print original filename for import errors if possible

>From upstream 94fc737b34e84a5753479910f8da97a28744f918

Changed paths:
    engines/ags/engine/script/cc_instance.cpp


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 96fe62be8d..fe71d254cf 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -1500,7 +1500,7 @@ bool ccInstance::ResolveScriptImports(PScript scri) {
 
 		resolved_imports[i] = _GP(simp).get_index_of(scri->imports[i]);
 		if (resolved_imports[i] < 0) {
-			cc_error("unresolved import '%s'", scri->imports[i]);
+			cc_error("unresolved import '%s' in %s", scri->imports[i], scri->numSections > 0 ? scri->sectionNames[0] : "<unknown>");
 			return false;
 		}
 	}


Commit: 7a61c3838a2bf3db66883ead75ba9f4cb7248d7d
    https://github.com/scummvm/scummvm/commit/7a61c3838a2bf3db66883ead75ba9f4cb7248d7d
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:39-07:00

Commit Message:
AGS: Added GetScriptAPIName() and use it instead of an array

>From upstream 20d8861d742a8ce61a9f5f72547d5f17025c2f0e

Changed paths:
    engines/ags/engine/game/game_init.cpp
    engines/ags/shared/ac/game_setup_struct_base.cpp
    engines/ags/shared/ac/game_struct_defines.h


diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index ca09ed8869..f853381390 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -281,12 +281,11 @@ HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion dat
 	const ScriptAPIVersion base_api = (ScriptAPIVersion)_GP(game).options[OPT_BASESCRIPTAPI];
 	const ScriptAPIVersion compat_api = (ScriptAPIVersion)_GP(game).options[OPT_SCRIPTCOMPATLEV];
 	if (data_ver >= kGameVersion_341) {
-		// TODO: find a way to either automate this list of strings or make it more visible (shared & easier to find in engine code)
-		// TODO: stack-allocated strings, here and in other similar places
-		const String scapi_names[kScriptAPI_Current + 1] = { "v3.2.1", "v3.3.0", "v3.3.4", "v3.3.5", "v3.4.0", "v3.4.1", "v3.5.0", "v3.5.0.7" };
+		const char *base_api_name = GetScriptAPIName(base_api);
+		const char *compat_api_name = GetScriptAPIName(compat_api);
 		Debug::Printf(kDbgMsg_Info, "Requested script API: %s (%d), compat level: %s (%d)",
-		              base_api >= 0 && base_api <= kScriptAPI_Current ? scapi_names[base_api].GetCStr() : "unknown", base_api,
-		              compat_api >= 0 && compat_api <= kScriptAPI_Current ? scapi_names[compat_api].GetCStr() : "unknown", compat_api);
+			base_api >= 0 && base_api <= kScriptAPI_Current ? base_api_name : "unknown", base_api,
+			compat_api >= 0 && compat_api <= kScriptAPI_Current ? compat_api_name : "unknown", compat_api);
 	}
 	// If the game was compiled using unsupported version of the script API,
 	// we warn about potential incompatibilities but proceed further.
diff --git a/engines/ags/shared/ac/game_setup_struct_base.cpp b/engines/ags/shared/ac/game_setup_struct_base.cpp
index 2ea471c01a..3cf1db24dd 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.cpp
+++ b/engines/ags/shared/ac/game_setup_struct_base.cpp
@@ -255,4 +255,19 @@ Size ResolutionTypeToSize(GameResolutionType resolution, bool letterbox) {
 	return Size();
 }
 
+const char *GetScriptAPIName(ScriptAPIVersion v) {
+	switch (v) {
+	case kScriptAPI_v321: return "v3.2.1";
+	case kScriptAPI_v330: return "v3.3.0";
+	case kScriptAPI_v334: return "v3.3.4";
+	case kScriptAPI_v335: return "v3.3.5";
+	case kScriptAPI_v340: return "v3.4.0";
+	case kScriptAPI_v341: return "v3.4.1";
+	case kScriptAPI_v350: return "v3.5.0-alpha";
+	case kScriptAPI_v3507: return "v3.5.0-final";
+	case kScriptAPI_v351: return "v3.5.1";
+	}
+	return "unknown";
+}
+
 } // namespace AGS3
diff --git a/engines/ags/shared/ac/game_struct_defines.h b/engines/ags/shared/ac/game_struct_defines.h
index d7259ca908..cb7d802663 100644
--- a/engines/ags/shared/ac/game_struct_defines.h
+++ b/engines/ags/shared/ac/game_struct_defines.h
@@ -169,6 +169,8 @@ enum ScriptAPIVersion {
 	kScriptAPI_Current = kScriptAPI_v351
 };
 
+extern const char *GetScriptAPIName(ScriptAPIVersion v);
+
 // Determines whether the graphics renderer should scale sprites at the final
 // screen resolution, as opposed to native resolution
 enum RenderAtScreenRes {


Commit: 4271f74cafcd52fc505c0462b20047f78e333b24
    https://github.com/scummvm/scummvm/commit/4271f74cafcd52fc505c0462b20047f78e333b24
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:39-07:00

Commit Message:
AGS: Added API level for v3.6.0

>From upstream 1ff5f4586373f140ebad65809d38aa084357eb4a

Changed paths:
    engines/ags/shared/ac/game_struct_defines.h


diff --git a/engines/ags/shared/ac/game_struct_defines.h b/engines/ags/shared/ac/game_struct_defines.h
index cb7d802663..5fd7f8d2b2 100644
--- a/engines/ags/shared/ac/game_struct_defines.h
+++ b/engines/ags/shared/ac/game_struct_defines.h
@@ -166,7 +166,8 @@ enum ScriptAPIVersion {
 	kScriptAPI_v350 = 6,
 	kScriptAPI_v3507 = 7,
 	kScriptAPI_v351 = 8,
-	kScriptAPI_Current = kScriptAPI_v351
+	kScriptAPI_v360 = 3060000,
+	kScriptAPI_Current = kScriptAPI_v360
 };
 
 extern const char *GetScriptAPIName(ScriptAPIVersion v);


Commit: 1b0cbbaafcfa3f5d039f62944987eb63ae689625
    https://github.com/scummvm/scummvm/commit/1b0cbbaafcfa3f5d039f62944987eb63ae689625
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:39-07:00

Commit Message:
AGS: implemented System.Log()

>From upstream 0c226592c679106e15f4ec3847ee0d57c2afae19

Changed paths:
    engines/ags/engine/ac/system.cpp
    engines/ags/shared/debugging/debug_manager.cpp


diff --git a/engines/ags/engine/ac/system.cpp b/engines/ags/engine/ac/system.cpp
index 8c7667b3dd..ac77d137bc 100644
--- a/engines/ags/engine/ac/system.cpp
+++ b/engines/ags/engine/ac/system.cpp
@@ -27,11 +27,13 @@
 #include "ags/shared/ac/game_setup_struct.h"
 #include "ags/engine/ac/game_state.h"
 #include "ags/engine/ac/global_debug.h"
+#include "ags/engine/ac/global_translation.h"
 #include "ags/engine/ac/mouse.h"
 #include "ags/engine/ac/string.h"
 #include "ags/engine/ac/system.h"
 #include "ags/engine/ac/dynobj/script_system.h"
 #include "ags/engine/debugging/debug_log.h"
+#include "ags/shared/debugging/out.h"
 #include "ags/engine/gfx/graphics_driver.h"
 #include "ags/engine/main/config.h"
 #include "ags/engine/main/graphics_mode.h"
@@ -48,6 +50,8 @@
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 bool System_HasInputFocus() {
 	return !_G(switched_away);
 }
@@ -330,7 +334,11 @@ RuntimeScriptValue Sc_System_SaveConfigToFile(const RuntimeScriptValue *params,
 	API_SCALL_VOID(save_config_file);
 }
 
-
+RuntimeScriptValue Sc_System_Log(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_SCRIPT_SPRINTF(Sc_System_Log, 2);
+	Debug::Printf(kDbgGroup_Script, (MessageType)params[0].IValue, "%s", scsf_buffer);
+	return RuntimeScriptValue((int32_t)0);
+}
 
 
 void RegisterSystemAPI() {
@@ -363,6 +371,7 @@ void RegisterSystemAPI() {
 	ccAddExternalStaticFunction("System::set_Windowed", Sc_System_SetWindowed);
 
 	ccAddExternalStaticFunction("System::SaveConfigToFile", Sc_System_SaveConfigToFile);
+	ccAddExternalStaticFunction("System::Log^102", Sc_System_Log);
 
 	/* ----------------------- Registering unsafe exports for plugins -----------------------*/
 
diff --git a/engines/ags/shared/debugging/debug_manager.cpp b/engines/ags/shared/debugging/debug_manager.cpp
index 374e07c2e9..083321d783 100644
--- a/engines/ags/shared/debugging/debug_manager.cpp
+++ b/engines/ags/shared/debugging/debug_manager.cpp
@@ -100,6 +100,7 @@ DebugManager::DebugManager() {
 	// Add hardcoded groups
 	RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_Main, "main"), ""));
 	RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_Game, "game"), "Game"));
+	RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_Script, "script"), "Script"));
 	RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_SprCache, "sprcache"), "Sprite cache"));
 	RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_ManObj, "manobj"), "Managed obj"));
 	_firstFreeGroupID = _groups.size();


Commit: 8fc03ec83b213a8ace0ac638fc75532b75aa2216
    https://github.com/scummvm/scummvm/commit/8fc03ec83b213a8ace0ac638fc75532b75aa2216
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:39-07:00

Commit Message:
AGS: Fixed DebugManager::RegisterGroup

>From upstream fa31d1bbe2d5e7af95887590d38f4c2233c5ba25

Changed paths:
    engines/ags/shared/debugging/debug_manager.cpp


diff --git a/engines/ags/shared/debugging/debug_manager.cpp b/engines/ags/shared/debugging/debug_manager.cpp
index 083321d783..a4b0b33fd4 100644
--- a/engines/ags/shared/debugging/debug_manager.cpp
+++ b/engines/ags/shared/debugging/debug_manager.cpp
@@ -138,8 +138,9 @@ DebugGroup DebugManager::RegisterGroup(const String &id, const String &out_name)
 }
 
 void DebugManager::RegisterGroup(const DebugGroup &group) {
-	_groups.push_back(group);
-	_groupByStrLookup[group.UID.SID] = group.UID;
+	if (_groups.size() <= group.UID.ID)
+		_groups.resize(group.UID.ID + 1);
+	_groups[group.UID.ID] = group;	_groupByStrLookup[group.UID.SID] = group.UID;
 }
 
 PDebugOutput DebugManager::RegisterOutput(const String &id, IOutputHandler *handler, MessageType def_verbosity, bool enabled) {


Commit: 04d6a41c428d120ba3af7cd1b7babae93e7f7eff
    https://github.com/scummvm/scummvm/commit/04d6a41c428d120ba3af7cd1b7babae93e7f7eff
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:40-07:00

Commit Message:
AGS: Simplified System.Log implementation

>From upstream 3d13f9056cd26b0d4da7b8e3207ad330e20a4799

Changed paths:
    engines/ags/engine/ac/system.cpp
    engines/ags/engine/script/script_api.h


diff --git a/engines/ags/engine/ac/system.cpp b/engines/ags/engine/ac/system.cpp
index ac77d137bc..161db5e3d1 100644
--- a/engines/ags/engine/ac/system.cpp
+++ b/engines/ags/engine/ac/system.cpp
@@ -335,8 +335,8 @@ RuntimeScriptValue Sc_System_SaveConfigToFile(const RuntimeScriptValue *params,
 }
 
 RuntimeScriptValue Sc_System_Log(const RuntimeScriptValue *params, int32_t param_count) {
-	API_SCALL_SCRIPT_SPRINTF(Sc_System_Log, 2);
-	Debug::Printf(kDbgGroup_Script, (MessageType)params[0].IValue, "%s", scsf_buffer);
+	API_SCALL_SCRIPT_SPRINTF_PURE(Sc_System_Log, 2);
+	Debug::Printf(kDbgGroup_Script, (MessageType)params[0].IValue, String::Wrapper(scsf_buffer));
 	return RuntimeScriptValue((int32_t)0);
 }
 
diff --git a/engines/ags/engine/script/script_api.h b/engines/ags/engine/script/script_api.h
index d7b30e5853..0ffbe8a705 100644
--- a/engines/ags/engine/script/script_api.h
+++ b/engines/ags/engine/script/script_api.h
@@ -82,7 +82,7 @@ inline const char *ScriptVSprintf(char *buffer, size_t buf_length, const char *f
 	return RuntimeScriptValue()
 
 //-----------------------------------------------------------------------------
-// Calls to ScriptSprintf
+// Calls to ScriptSprintf with automatic translation
 
 #define API_SCALL_SCRIPT_SPRINTF(FUNCTION, PARAM_COUNT) \
 	ASSERT_PARAM_COUNT(FUNCTION, PARAM_COUNT); \
@@ -94,6 +94,15 @@ inline const char *ScriptVSprintf(char *buffer, size_t buf_length, const char *f
 	char ScSfBuffer[STD_BUFFER_SIZE]; \
 	const char *scsf_buffer = ScriptSprintf(ScSfBuffer, STD_BUFFER_SIZE, get_translation(params[PARAM_COUNT - 1].Ptr), params + PARAM_COUNT, param_count - PARAM_COUNT)
 
+//-----------------------------------------------------------------------------
+// Calls to ScriptSprintf without translation
+
+#define API_SCALL_SCRIPT_SPRINTF_PURE(FUNCTION, PARAM_COUNT) \
+    ASSERT_PARAM_COUNT(FUNCTION, PARAM_COUNT); \
+    char ScSfBuffer[STD_BUFFER_SIZE]; \
+    const char *scsf_buffer = ScriptSprintf(ScSfBuffer, STD_BUFFER_SIZE, params[PARAM_COUNT - 1].Ptr, params + PARAM_COUNT, param_count - PARAM_COUNT)
+
+
 //-----------------------------------------------------------------------------
 // Calls to ScriptSprintfV (unsafe plugin variant)
 


Commit: a8f4d9dc5bcf98de0a6f915649106629e2206783
    https://github.com/scummvm/scummvm/commit/a8f4d9dc5bcf98de0a6f915649106629e2206783
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:40-07:00

Commit Message:
AGS: Safety check for asset library v20, for asset name over limit

>From upstream 9a2aaae2b3a0799722f1567f97761701afbef584

Changed paths:
    engines/ags/shared/util/multi_file_lib.cpp
    engines/ags/shared/util/multi_file_lib.h


diff --git a/engines/ags/shared/util/multi_file_lib.cpp b/engines/ags/shared/util/multi_file_lib.cpp
index 9e6d83cb03..f574519fc2 100644
--- a/engines/ags/shared/util/multi_file_lib.cpp
+++ b/engines/ags/shared/util/multi_file_lib.cpp
@@ -258,6 +258,8 @@ MFLUtil::MFLError MFLUtil::ReadV20(AssetLibInfo &lib, Stream *in) {
 	for (size_t i = 0; i < asset_count; ++i) {
 		short len = in->ReadInt16();
 		len /= 5; // CHECKME: why 5?
+		if (len > MaxAssetFileLen)
+			return kMFLErrAssetNameLong;
 		in->Read(fn_buf, len);
 		// decrypt filenames
 		DecryptText(fn_buf);
diff --git a/engines/ags/shared/util/multi_file_lib.h b/engines/ags/shared/util/multi_file_lib.h
index 4e88394a0b..226d0bfd63 100644
--- a/engines/ags/shared/util/multi_file_lib.h
+++ b/engines/ags/shared/util/multi_file_lib.h
@@ -51,6 +51,7 @@ enum MFLError {
 	kMFLErrLibVersion = -2, // library version unsupported
 	kMFLErrNoLibBase = -3, // file is not library base (head)
 	kMFLErrLibAssetCount = -4, // too many assets in library
+	kMFLErrAssetNameLong = -5  // asset name is too long (old formats only)
 };
 
 enum MFLVersion {


Commit: 7d01f1dc1f681106da668a293102c4c4810c80b4
    https://github.com/scummvm/scummvm/commit/7d01f1dc1f681106da668a293102c4c4810c80b4
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:40-07:00

Commit Message:
AGS: in asset library files treat offsets & sizes as unsigned values

>From upstream 603c5c27165e46afc52b06eed2a9f798ae2ecf4e

Changed paths:
    engines/ags/shared/util/multi_file_lib.cpp


diff --git a/engines/ags/shared/util/multi_file_lib.cpp b/engines/ags/shared/util/multi_file_lib.cpp
index f574519fc2..5cb35d265b 100644
--- a/engines/ags/shared/util/multi_file_lib.cpp
+++ b/engines/ags/shared/util/multi_file_lib.cpp
@@ -165,7 +165,7 @@ MFLUtil::MFLError MFLUtil::ReadSingleFileLib(AssetLibInfo &lib, Stream *in, MFLV
 	int passwmodifier = in->ReadByte();
 	in->ReadInt8(); // unused byte
 	lib.LibFileNames.resize(1); // only one library part
-	size_t asset_count = in->ReadInt16();
+	size_t asset_count = (uint16)in->ReadInt16();
 	lib.AssetInfos.resize(asset_count);
 
 	in->Seek(SingleFilePswLen, kSeekCurrent); // skip password dooberry
@@ -180,7 +180,7 @@ MFLUtil::MFLError MFLUtil::ReadSingleFileLib(AssetLibInfo &lib, Stream *in, MFLV
 		lib.AssetInfos[i].LibUid = 0;
 	}
 	for (size_t i = 0; i < asset_count; ++i) {
-		lib.AssetInfos[i].Size = in->ReadInt32();
+		lib.AssetInfos[i].Size = (uint32)in->ReadInt32();
 	}
 	in->Seek(2 * asset_count, kSeekCurrent); // skip flags & ratio
 	lib.AssetInfos[0].Offset = in->GetPosition();
@@ -213,7 +213,7 @@ MFLUtil::MFLError MFLUtil::ReadMultiFileLib(AssetLibInfo &lib, Stream *in, MFLVe
 
 MFLUtil::MFLError MFLUtil::ReadV10(AssetLibInfo &lib, Stream *in, MFLVersion lib_version) {
 	// number of clib parts
-	size_t mf_count = in->ReadInt32();
+	size_t mf_count = (uint32)in->ReadInt32();
 	lib.LibFileNames.resize(mf_count);
 	// filenames for all clib parts; filenames are only 20 chars long in this format version
 	for (size_t i = 0; i < mf_count; ++i) {
@@ -221,7 +221,7 @@ MFLUtil::MFLError MFLUtil::ReadV10(AssetLibInfo &lib, Stream *in, MFLVersion lib
 	}
 
 	// number of files in clib
-	size_t asset_count = in->ReadInt32();
+	size_t asset_count = (uint32)in->ReadInt32();
 	// read information on clib contents
 	lib.AssetInfos.resize(asset_count);
 	// filename array is only 25 chars long in this format version
@@ -233,17 +233,17 @@ MFLUtil::MFLError MFLUtil::ReadV10(AssetLibInfo &lib, Stream *in, MFLVersion lib
 		lib.AssetInfos[i].FileName = fn_buf;
 	}
 	for (size_t i = 0; i < asset_count; ++i)
-		lib.AssetInfos[i].Offset = in->ReadInt32();
+		lib.AssetInfos[i].Offset = (uint32)in->ReadInt32();
 	for (size_t i = 0; i < asset_count; ++i)
-		lib.AssetInfos[i].Size = in->ReadInt32();
+		lib.AssetInfos[i].Size = (uint32)in->ReadInt32();
 	for (size_t i = 0; i < asset_count; ++i)
-		lib.AssetInfos[i].LibUid = in->ReadInt8();
+		lib.AssetInfos[i].LibUid = (uint32)in->ReadInt8();
 	return kMFLNoError;
 }
 
 MFLUtil::MFLError MFLUtil::ReadV20(AssetLibInfo &lib, Stream *in) {
 	// number of clib parts
-	size_t mf_count = in->ReadInt32();
+	size_t mf_count = (uint32)in->ReadInt32();
 	lib.LibFileNames.resize(mf_count);
 	// filenames for all clib parts
 	for (size_t i = 0; i < mf_count; ++i) {
@@ -251,12 +251,12 @@ MFLUtil::MFLError MFLUtil::ReadV20(AssetLibInfo &lib, Stream *in) {
 	}
 
 	// number of files in clib
-	size_t asset_count = in->ReadInt32();
+	size_t asset_count = (uint32)in->ReadInt32();
 	// read information on clib contents
 	lib.AssetInfos.resize(asset_count);
 	char fn_buf[MaxAssetFileLen];
 	for (size_t i = 0; i < asset_count; ++i) {
-		short len = in->ReadInt16();
+		size_t len = in->ReadInt16();
 		len /= 5; // CHECKME: why 5?
 		if (len > MaxAssetFileLen)
 			return kMFLErrAssetNameLong;
@@ -266,11 +266,11 @@ MFLUtil::MFLError MFLUtil::ReadV20(AssetLibInfo &lib, Stream *in) {
 		lib.AssetInfos[i].FileName = fn_buf;
 	}
 	for (size_t i = 0; i < asset_count; ++i)
-		lib.AssetInfos[i].Offset = in->ReadInt32();
+		lib.AssetInfos[i].Offset = (uint32)in->ReadInt32();
 	for (size_t i = 0; i < asset_count; ++i)
-		lib.AssetInfos[i].Size = in->ReadInt32();
+		lib.AssetInfos[i].Size = (uint32)in->ReadInt32();
 	for (size_t i = 0; i < asset_count; ++i)
-		lib.AssetInfos[i].LibUid = in->ReadInt8();
+		lib.AssetInfos[i].LibUid = (uint32)in->ReadInt8();
 	return kMFLNoError;
 }
 
@@ -278,7 +278,7 @@ MFLUtil::MFLError MFLUtil::ReadV21(AssetLibInfo &lib, Stream *in) {
 	// init randomizer
 	int rand_val = in->ReadInt32() + EncryptionRandSeed;
 	// number of clib parts
-	size_t mf_count = ReadEncInt32(in, rand_val);
+	size_t mf_count = (uint32)ReadEncInt32(in, rand_val);
 	lib.LibFileNames.resize(mf_count);
 	// filenames for all clib parts
 	char fn_buf[MaxDataFileLen > MaxAssetFileLen ? MaxDataFileLen : MaxAssetFileLen];
@@ -288,7 +288,7 @@ MFLUtil::MFLError MFLUtil::ReadV21(AssetLibInfo &lib, Stream *in) {
 	}
 
 	// number of files in clib
-	size_t asset_count = ReadEncInt32(in, rand_val);
+	size_t asset_count = (uint32)ReadEncInt32(in, rand_val);
 	// read information on clib contents
 	lib.AssetInfos.resize(asset_count);
 	for (size_t i = 0; i < asset_count; ++i) {
@@ -296,11 +296,11 @@ MFLUtil::MFLError MFLUtil::ReadV21(AssetLibInfo &lib, Stream *in) {
 		lib.AssetInfos[i].FileName = fn_buf;
 	}
 	for (size_t i = 0; i < asset_count; ++i)
-		lib.AssetInfos[i].Offset = ReadEncInt32(in, rand_val);
+		lib.AssetInfos[i].Offset = (uint32)ReadEncInt32(in, rand_val);
 	for (size_t i = 0; i < asset_count; ++i)
-		lib.AssetInfos[i].Size = ReadEncInt32(in, rand_val);
+		lib.AssetInfos[i].Size = (uint32)ReadEncInt32(in, rand_val);
 	for (size_t i = 0; i < asset_count; ++i)
-		lib.AssetInfos[i].LibUid = ReadEncInt8(in, rand_val);
+		lib.AssetInfos[i].LibUid = (uint32)ReadEncInt8(in, rand_val);
 	return kMFLNoError;
 }
 
@@ -310,19 +310,19 @@ MFLUtil::MFLError MFLUtil::ReadV30(AssetLibInfo &lib, Stream *in, MFLVersion /*
 	// as one of the options here.
 	/* int flags = */ in->ReadInt32(); // reserved options
 	// number of clib parts
-	size_t mf_count = in->ReadInt32();
+	size_t mf_count = (uint32)in->ReadInt32();
 	lib.LibFileNames.resize(mf_count);
 	// filenames for all clib parts
 	for (size_t i = 0; i < mf_count; ++i)
 		lib.LibFileNames[i] = String::FromStream(in);
 
 	// number of files in clib
-	size_t asset_count = in->ReadInt32();
+	size_t asset_count = (uint32)in->ReadInt32();
 	// read information on clib contents
 	lib.AssetInfos.resize(asset_count);
 	for (auto &asset : lib.AssetInfos) {
 		asset.FileName = String::FromStream(in);
-		asset.LibUid = in->ReadInt8();
+		asset.LibUid = (uint8)in->ReadInt8();
 		asset.Offset = in->ReadInt64();
 		asset.Size = in->ReadInt64();
 	}


Commit: 17b7626d110720c1f39a6b57c3400a62202219ca
    https://github.com/scummvm/scummvm/commit/17b7626d110720c1f39a6b57c3400a62202219ca
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:40-07:00

Commit Message:
AGS: don't mark viewport & camera as "changed" when not necessary

>From upstream ecce694c7d93206cae7aac51b302b58aa513e3dc

Changed paths:
    engines/ags/engine/game/viewport.cpp
    engines/ags/shared/util/geometry.h


diff --git a/engines/ags/engine/game/viewport.cpp b/engines/ags/engine/game/viewport.cpp
index 84c0cb72f7..f01e433875 100644
--- a/engines/ags/engine/game/viewport.cpp
+++ b/engines/ags/engine/game/viewport.cpp
@@ -46,6 +46,8 @@ void Camera::SetSize(const Size cam_size) {
 	// (or rather - looking outside of the room background); look into this later
 	const Size real_room_sz = Size(data_to_game_coord(_GP(thisroom).Width), data_to_game_coord(_GP(thisroom).Height));
 	Size real_size = Size::Clamp(cam_size, Size(1, 1), real_room_sz);
+	if (_position.GetWidth() == real_size.Width && _position.GetHeight() == real_size.Height)
+		return;
 
 	_position.SetWidth(real_size.Width);
 	_position.SetHeight(real_size.Height);
@@ -66,6 +68,8 @@ void Camera::SetAt(int x, int y) {
 	int room_height = data_to_game_coord(_GP(thisroom).Height);
 	x = Math::Clamp(x, 0, room_width - cw);
 	y = Math::Clamp(y, 0, room_height - ch);
+	if (_position.Left == x && _position.Top == y)
+		return;
 	_position.MoveTo(Point(x, y));
 	_hasChangedPosition = true;
 }
@@ -129,7 +133,10 @@ void Viewport::SetID(int id) {
 void Viewport::SetRect(const Rect &rc) {
 	// TODO: consider allowing size 0,0, in which case viewport is considered not visible
 	Size fix_size = rc.GetSize().IsNull() ? Size(1, 1) : rc.GetSize();
-	_position = RectWH(rc.Left, rc.Top, fix_size.Width, fix_size.Height);
+	Rect new_pos = RectWH(rc.Left, rc.Top, fix_size.Width, fix_size.Height);
+	if (new_pos == _position)
+		return;
+	_position = new_pos;
 	AdjustTransformation();
 	_hasChangedPosition = true;
 	_hasChangedSize = true;
@@ -138,12 +145,16 @@ void Viewport::SetRect(const Rect &rc) {
 void Viewport::SetSize(const Size sz) {
 	// TODO: consider allowing size 0,0, in which case viewport is considered not visible
 	Size fix_size = sz.IsNull() ? Size(1, 1) : sz;
+	if (_position.GetWidth() == fix_size.Width && _position.GetHeight() == fix_size.Height)
+		return;
 	_position = RectWH(_position.Left, _position.Top, fix_size.Width, fix_size.Height);
 	AdjustTransformation();
 	_hasChangedSize = true;
 }
 
 void Viewport::SetAt(int x, int y) {
+	if (_position.Left == x && _position.Top == y)
+		return;
 	_position.MoveTo(Point(x, y));
 	AdjustTransformation();
 	_hasChangedPosition = true;
diff --git a/engines/ags/shared/util/geometry.h b/engines/ags/shared/util/geometry.h
index 16ac4c5d8a..974292c4b7 100644
--- a/engines/ags/shared/util/geometry.h
+++ b/engines/ags/shared/util/geometry.h
@@ -283,6 +283,11 @@ struct Rect {
 	inline static Rect MoveBy(const Rect &r, int x, int y) {
 		return Rect(r.Left + x, r.Top + y, r.Right + x, r.Bottom + y);
 	}
+
+	inline bool operator ==(const Rect &r) const {
+		return Left == r.Left && Top == r.Top &&
+			Right == r.Right && Bottom == r.Bottom;
+	}
 };
 
 // Helper factory function


Commit: 4c5d1335a798214ee03fb0dc20e2563517ea1ff6
    https://github.com/scummvm/scummvm/commit/4c5d1335a798214ee03fb0dc20e2563517ea1ff6
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:41-07:00

Commit Message:
AGS: fixed software mode's dirty rects in the legacy letterbox mode

>From upstream b7fdf44a70d838a0848cd5c94ffe82821323c798

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw_software.cpp
    engines/ags/engine/ac/draw_software.h
    engines/ags/engine/ac/room.cpp
    engines/ags/globals.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 95dda4b642..96b0b55088 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -405,8 +405,11 @@ void dispose_room_drawdata() {
 
 void on_mainviewport_changed() {
 	if (!_G(gfxDriver)->RequiresFullRedrawEachFrame()) {
-		init_invalid_regions(-1, _GP(play).GetMainViewport().GetSize(), RectWH(_GP(play).GetMainViewport().GetSize()));
-		if (_GP(game).GetGameRes().ExceedsByAny(_GP(play).GetMainViewport().GetSize()))
+		const auto &view = _GP(play).GetMainViewport();
+		set_invalidrects_globaloffs(view.Left, view.Top);
+		// the black background region covers whole game screen
+		init_invalid_regions(-1, _GP(game).GetGameRes(), RectWH(_GP(game).GetGameRes()));
+		if (_GP(game).GetGameRes().ExceedsByAny(view.GetSize()))
 			clear_letterbox_borders();
 	}
 }
@@ -447,7 +450,10 @@ void prepare_roomview_frame(Viewport *view) {
 void sync_roomview(Viewport *view) {
 	if (view->GetCamera() == nullptr)
 		return;
-	init_invalid_regions(view->GetID(), view->GetCamera()->GetRect().GetSize(), _GP(play).GetRoomViewportAbs(view->GetID()));
+	// Note the dirty regions' viewport is found using absolute offset on game screen
+	init_invalid_regions(view->GetID(),
+		view->GetCamera()->GetRect().GetSize(),
+		_GP(play).GetRoomViewportAbs(view->GetID()));
 	prepare_roomview_frame(view);
 }
 
@@ -550,12 +556,10 @@ void invalidate_camera_frame(int index) {
 }
 
 void invalidate_rect(int x1, int y1, int x2, int y2, bool in_room) {
-	//if (!in_room)
 	invalidate_rect_ds(x1, y1, x2, y2, in_room);
 }
 
 void invalidate_sprite(int x1, int y1, IDriverDependantBitmap *pic, bool in_room) {
-	//if (!in_room)
 	invalidate_rect_ds(x1, y1, x1 + pic->GetWidth(), y1 + pic->GetHeight(), in_room);
 }
 
diff --git a/engines/ags/engine/ac/draw_software.cpp b/engines/ags/engine/ac/draw_software.cpp
index d7aa4dfd66..da3bdac1f6 100644
--- a/engines/ags/engine/ac/draw_software.cpp
+++ b/engines/ags/engine/ac/draw_software.cpp
@@ -122,6 +122,10 @@ void dispose_invalid_regions(bool /* room_only */) {
 	_GP(RoomCamPositions).clear();
 }
 
+void set_invalidrects_globaloffs(int x, int y) {
+	_GP(GlobalOffs) = Point(x, y);
+}
+
 void init_invalid_regions(int view_index, const Size &surf_size, const Rect &viewport) {
 	if (view_index < 0) {
 		_GP(BlackRects).Init(surf_size, viewport);
@@ -273,6 +277,7 @@ void invalidate_rect_ds(DirtyRects &rects, int x1, int y1, int x2, int y2, bool
 		y1 = rects.Screen2DirtySurf.Y.ScalePt(y1);
 		y2 = rects.Screen2DirtySurf.Y.ScalePt(y2);
 	} else {
+		// Transform only from camera pos to room background
 		x1 -= rects.Room2Screen.X.GetSrcOffset();
 		y1 -= rects.Room2Screen.Y.GetSrcOffset();
 		x2 -= rects.Room2Screen.X.GetSrcOffset();
@@ -283,6 +288,13 @@ void invalidate_rect_ds(DirtyRects &rects, int x1, int y1, int x2, int y2, bool
 }
 
 void invalidate_rect_ds(int x1, int y1, int x2, int y2, bool in_room) {
+	if (!in_room) { // convert from game viewport to global screen coords
+		x1 += _GP(GlobalOffs).X;
+		x2 += _GP(GlobalOffs).X;
+		y1 += _GP(GlobalOffs).Y;
+		y2 += _GP(GlobalOffs).Y;
+	}
+
 	for (auto &rects : _GP(RoomCamRects))
 		invalidate_rect_ds(rects, x1, y1, x2, y2, in_room);
 }
diff --git a/engines/ags/engine/ac/draw_software.h b/engines/ags/engine/ac/draw_software.h
index 02b2100c21..d5a425e03b 100644
--- a/engines/ags/engine/ac/draw_software.h
+++ b/engines/ags/engine/ac/draw_software.h
@@ -87,6 +87,8 @@ struct DirtyRects {
 	void Reset();
 };
 
+// Sets global viewport offset (used for legacy letterbox)
+void set_invalidrects_globaloffs(int x, int y);
 // Inits dirty rects array for the given room camera/viewport pair
 // View_index indicates the room viewport (>= 0) or the main viewport (-1)
 void init_invalid_regions(int view_index, const Size &surf_size, const Rect &viewport);
@@ -100,6 +102,7 @@ void set_invalidrects_cameraoffs(int view_index, int x, int y);
 void invalidate_all_rects();
 // Mark the whole camera surface dirty
 void invalidate_all_camera_rects(int view_index);
+// Mark certain rectangle dirty; in_room tells if coordinates are room viewport or screen coords
 void invalidate_rect_ds(int x1, int y1, int x2, int y2, bool in_room);
 // Paints the black screen background in the regions marked as dirty
 void update_black_invreg_and_reset(AGS::Shared::Bitmap *ds);
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index d5121e718f..1bc27c6ce5 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -368,6 +368,7 @@ void update_letterbox_mode() {
 
 	_GP(play).SetMainViewport(CenterInRect(game_frame, new_main_view));
 	_GP(play).SetUIViewport(new_main_view);
+	on_mainviewport_changed();
 }
 
 // Automatically reset primary room viewport and camera to match the new room size
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 1c2e766950..aa2fddc7a0 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -176,6 +176,7 @@ Globals::Globals() {
 
 	// draw_software.cpp globals
 	_BlackRects = new DirtyRects();
+	_GlobalOffs = new Point();
 	_RoomCamRects = new std::vector<DirtyRects>();
 	_RoomCamPositions = new std::vector<std::pair<int, int> >();
 
@@ -406,6 +407,7 @@ Globals::~Globals() {
 
 	// draw_software.cpp globals
 	delete _BlackRects;
+	delete _GlobalOffs;
 	delete _RoomCamRects;
 	delete _RoomCamPositions;
 
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 12e07cd071..71a94dffe4 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -138,6 +138,7 @@ struct NonBlockingScriptFunction;
 struct ObjectCache;
 struct OnScreenWindow;
 struct PluginObjectReader;
+struct Point;
 struct ResourcePaths;
 struct RGB_MAP;
 struct RoomCameraDrawData;
@@ -589,10 +590,11 @@ public:
 	 * @{
 	 */
 
-	// Dirty rects for the main viewport background (black screen);
+	// Dirty rects for the game screen background (black screen);
 	// these are used when the room viewport does not cover whole screen,
 	// so that we know when to paint black after mouse cursor and gui.
 	DirtyRects *_BlackRects;
+	Point *_GlobalOffs;
 	// Dirty rects object for the single room camera
 	std::vector<DirtyRects> *_RoomCamRects;
 	// Saved room camera offsets to know if we must invalidate whole surface.


Commit: c34807897ee43161db412b2413d6362799658c0f
    https://github.com/scummvm/scummvm/commit/c34807897ee43161db412b2413d6362799658c0f
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-10T14:33:41-07:00

Commit Message:
AGS: separate dirty rects function for the engine overlay sprites

>From upstream ef78805ffc0e3702dd329401c19116a7d4737470

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw_software.cpp
    engines/ags/engine/ac/draw_software.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 96b0b55088..6fb675d8aa 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -563,6 +563,10 @@ void invalidate_sprite(int x1, int y1, IDriverDependantBitmap *pic, bool in_room
 	invalidate_rect_ds(x1, y1, x1 + pic->GetWidth(), y1 + pic->GetHeight(), in_room);
 }
 
+void invalidate_sprite_glob(int x1, int y1, IDriverDependantBitmap *pic) {
+	invalidate_rect_global(x1, y1, x1 + pic->GetWidth(), y1 + pic->GetHeight());
+}
+
 void mark_current_background_dirty() {
 	_G(current_background_is_dirty) = true;
 }
@@ -1863,7 +1867,7 @@ void draw_fps(const Rect &viewport) {
 		ddb = _G(gfxDriver)->CreateDDBFromBitmap(fpsDisplay, false);
 	int yp = viewport.GetHeight() - fpsDisplay->GetHeight();
 	_G(gfxDriver)->DrawSprite(1, yp, ddb);
-	invalidate_sprite(1, yp, ddb, false);
+	invalidate_sprite_glob(1, yp, ddb);
 }
 
 // Draw GUI and overlays of all kinds, anything outside the room space
@@ -2204,7 +2208,7 @@ void construct_engine_overlay() {
 			_G(gfxDriver)->UpdateDDBFromBitmap(_G(debugConsole), _G(debugConsoleBuffer), false);
 
 		_G(gfxDriver)->DrawSprite(0, 0, _G(debugConsole));
-		invalidate_sprite(0, 0, _G(debugConsole), false);
+		invalidate_sprite_glob(0, 0, _G(debugConsole));
 	}
 
 	if (_G(display_fps) != kFPS_Hide)
diff --git a/engines/ags/engine/ac/draw_software.cpp b/engines/ags/engine/ac/draw_software.cpp
index da3bdac1f6..1cde520996 100644
--- a/engines/ags/engine/ac/draw_software.cpp
+++ b/engines/ags/engine/ac/draw_software.cpp
@@ -299,6 +299,11 @@ void invalidate_rect_ds(int x1, int y1, int x2, int y2, bool in_room) {
 		invalidate_rect_ds(rects, x1, y1, x2, y2, in_room);
 }
 
+void invalidate_rect_global(int x1, int y1, int x2, int y2) {
+	for (auto &rects : _GP(RoomCamRects))
+		invalidate_rect_ds(rects, x1, y1, x2, y2, false);
+}
+
 // Note that this function is denied to perform any kind of scaling or other transformation
 // other than blitting with offset. This is mainly because destination could be a 32-bit virtual screen
 // while room background was 16-bit and Allegro lib does not support stretching between colour depths.
diff --git a/engines/ags/engine/ac/draw_software.h b/engines/ags/engine/ac/draw_software.h
index d5a425e03b..cc88302c0a 100644
--- a/engines/ags/engine/ac/draw_software.h
+++ b/engines/ags/engine/ac/draw_software.h
@@ -104,6 +104,8 @@ void invalidate_all_rects();
 void invalidate_all_camera_rects(int view_index);
 // Mark certain rectangle dirty; in_room tells if coordinates are room viewport or screen coords
 void invalidate_rect_ds(int x1, int y1, int x2, int y2, bool in_room);
+// Mark rectangle dirty, treat pos as global screen coords (not offset by legacy letterbox mode)
+void invalidate_rect_global(int x1, int y1, int x2, int y2);
 // Paints the black screen background in the regions marked as dirty
 void update_black_invreg_and_reset(AGS::Shared::Bitmap *ds);
 // Copies the room regions marked as dirty from source (src) to destination (ds) with the given offset (x, y)




More information about the Scummvm-git-logs mailing list