[Scummvm-git-logs] scummvm master -> 38f0fdc47bee954f4c39ae66ea6b959a3c8437cb

dreammaster noreply at scummvm.org
Sun Apr 24 05:26:11 UTC 2022


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

Summary:
3467180e39 AGS: Refactored GUIMain to not use mouse coords directly
17bac5b1ef AGS: Removed redundant function domouse(int what)
35e6c82ac8 AGS: Don't use global loadSaveGameOnStartup beyond starting function
1d38174881 AGS: Fixed new SpriteFileWriter saving of 16-bit sprites w palette
1f666cf0dc AGS: Fixed loading a different game when restoring a save
9dd7ce9313 AGS: Store debug room names in std::vector
51a0c34352 AGS: Serialize dynamic objects using MemoryStream
ecb7e42439 AGS: unserialize dynamic objects using MemoryStream
d3f9f299dd * Engine: moved unsupported scStartRecording, get rid of a h/cpp pair
38f0fdc47b AGS: Renamed locals hiding global mouse vars in InventoryScreen


Commit: 3467180e39a49fdeeee5938a8cf00904629e2be4
    https://github.com/scummvm/scummvm/commit/3467180e39a49fdeeee5938a8cf00904629e2be4
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-04-23T17:56:20-07:00

Commit Message:
AGS: Refactored GUIMain to not use mouse coords directly

>From upstream 2f736d3365dc5fae6eebe353b64e16af156308f8

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/global_gui.cpp
    engines/ags/engine/ac/global_inventory_item.cpp
    engines/ags/engine/ac/gui.cpp
    engines/ags/engine/ac/gui_control.cpp
    engines/ags/engine/gui/csci_dialog.cpp
    engines/ags/shared/gui/gui_main.cpp
    engines/ags/shared/gui/gui_main.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index d9a364e3429..d27c01677b8 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -2029,7 +2029,7 @@ void draw_gui_and_overlays() {
 						(_G(all_buttons_disabled) >= 0) &&
 						(_GP(guis)[gg].PopupStyle != kGUIPopupNoAutoRemove))
 					continue;
-				_GP(guis)[gg].Poll();
+				_GP(guis)[gg].Poll(_G(mousex), _G(mousey));
 			}
 		}
 	}
diff --git a/engines/ags/engine/ac/global_gui.cpp b/engines/ags/engine/ac/global_gui.cpp
index 74035f542b9..c31c7ac3ad0 100644
--- a/engines/ags/engine/ac/global_gui.cpp
+++ b/engines/ags/engine/ac/global_gui.cpp
@@ -77,7 +77,7 @@ void InterfaceOn(int ifn) {
 	if (_GP(guis)[ifn].PopupStyle == kGUIPopupModal) PauseGame();
 	// clear the cached mouse position
 	_GP(guis)[ifn].OnControlPositionChanged();
-	_GP(guis)[ifn].Poll();
+	_GP(guis)[ifn].Poll(_G(mousex), _G(mousey));
 }
 
 void InterfaceOff(int ifn) {
diff --git a/engines/ags/engine/ac/global_inventory_item.cpp b/engines/ags/engine/ac/global_inventory_item.cpp
index c983da07e21..64f2e6974de 100644
--- a/engines/ags/engine/ac/global_inventory_item.cpp
+++ b/engines/ags/engine/ac/global_inventory_item.cpp
@@ -66,20 +66,16 @@ void SetInvItemName(int invi, const char *newName) {
 	GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
 }
 
-int GetInvAt(int xxx, int yyy) {
-	int ongui = GetGUIAt(xxx, yyy);
+int GetInvAt(int atx, int aty) {
+	int ongui = GetGUIAt(atx, aty);
 	if (ongui >= 0) {
-		int mxwas = _G(mousex), mywas = _G(mousey);
-		_G(mousex) = data_to_game_coord(xxx) - _GP(guis)[ongui].X;
-		_G(mousey) = data_to_game_coord(yyy) - _GP(guis)[ongui].Y;
-		int onobj = _GP(guis)[ongui].FindControlUnderMouse();
+		data_to_game_coords(&atx, &aty);
+		int onobj = _GP(guis)[ongui].FindControlAt(atx, aty);
 		GUIObject *guio = _GP(guis)[ongui].GetControl(onobj);
 		if (guio) {
-			_G(mouse_ifacebut_xoffs) = _G(mousex) - (guio->X);
-			_G(mouse_ifacebut_yoffs) = _G(mousey) - (guio->Y);
+			_G(mouse_ifacebut_xoffs) = atx - _GP(guis)[ongui].X - guio->X;
+			_G(mouse_ifacebut_yoffs) = aty - _GP(guis)[ongui].Y - guio->Y;
 		}
-		_G(mousex) = mxwas;
-		_G(mousey) = mywas;
 		if (guio && (_GP(guis)[ongui].GetControlType(onobj) == kGUIInvWindow))
 			return offset_over_inv((GUIInvWindow *)guio);
 	}
diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index 519a655214f..a27f9cbd514 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -278,15 +278,9 @@ void GUI_Click(ScriptGUI *scgui, int mbut) {
 void GUI_ProcessClick(int x, int y, int mbut) {
 	int guiid = gui_get_interactable(x, y);
 	if (guiid >= 0) {
-		const int real_mousex = _G(mousex);
-		const int real_mousey = _G(mousey);
-		_G(mousex) = x;
-		_G(mousey) = y;
-		_GP(guis)[guiid].Poll();
+		_GP(guis)[guiid].Poll(x, y);
 		gui_on_mouse_down(guiid, mbut);
 		gui_on_mouse_up(guiid, mbut);
-		_G(mousex) = real_mousex;
-		_G(mousey) = real_mousey;
 	}
 }
 
@@ -613,7 +607,7 @@ void gui_on_mouse_up(const int wasongui, const int wasbutdown) {
 
 void gui_on_mouse_down(const int guin, const int mbut) {
 	debug_script_log("Mouse click over GUI %d", guin);
-	_GP(guis)[guin].OnMouseButtonDown();
+	_GP(guis)[guin].OnMouseButtonDown(_G(mousex), _G(mousey));
 	// run GUI click handler if not on any control
 	if ((_GP(guis)[guin].MouseDownCtrl < 0) && (!_GP(guis)[guin].OnClickHandler.IsEmpty()))
 		force_event(EV_IFACECLICK, guin, -1, mbut);
diff --git a/engines/ags/engine/ac/gui_control.cpp b/engines/ags/engine/ac/gui_control.cpp
index f174bb1cc91..05561ff0e56 100644
--- a/engines/ags/engine/ac/gui_control.cpp
+++ b/engines/ags/engine/ac/gui_control.cpp
@@ -22,6 +22,7 @@
 #include "ags/shared/ac/common.h"
 #include "ags/engine/ac/gui_control.h"
 #include "ags/engine/ac/global_gui.h"
+#include "ags/engine/ac/mouse.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/shared/gui/gui_button.h"
 #include "ags/shared/gui/gui_inv.h"
@@ -48,13 +49,8 @@ GUIObject *GetGUIControlAtLocation(int xx, int yy) {
 		return nullptr;
 
 	data_to_game_coords(&xx, &yy);
+	int toret = _GP(guis)[guinum].FindControlAt(xx, yy, 0, false);
 
-	int oldmousex = _G(mousex), oldmousey = _G(mousey);
-	_G(mousex) = xx - _GP(guis)[guinum].X;
-	_G(mousey) = yy - _GP(guis)[guinum].Y;
-	int toret = _GP(guis)[guinum].FindControlUnderMouse(0, false);
-	_G(mousex) = oldmousex;
-	_G(mousey) = oldmousey;
 	if (toret < 0)
 		return nullptr;
 
diff --git a/engines/ags/engine/gui/csci_dialog.cpp b/engines/ags/engine/gui/csci_dialog.cpp
index 083ba25f808..ca49b26d7c8 100644
--- a/engines/ags/engine/gui/csci_dialog.cpp
+++ b/engines/ags/engine/gui/csci_dialog.cpp
@@ -19,7 +19,7 @@
  *
  */
 
-//include <cctype>
+#include "ags/engine/gui/csci_dialog.h"
 #include "ags/shared/ac/common.h"
 #include "ags/engine/ac/draw.h"
 #include "ags/engine/ac/game_setup.h"
diff --git a/engines/ags/shared/gui/gui_main.cpp b/engines/ags/shared/gui/gui_main.cpp
index a9d3508784c..3145be1303d 100644
--- a/engines/ags/shared/gui/gui_main.cpp
+++ b/engines/ags/shared/gui/gui_main.cpp
@@ -91,7 +91,12 @@ void GUIMain::InitDefaults() {
 	_ctrlDrawOrder.clear();
 }
 
-int32_t GUIMain::FindControlUnderMouse(int leeway, bool must_be_clickable) const {
+int GUIMain::FindControlAt(int atx, int aty, int leeway, bool must_be_clickable) const {
+	// translate to GUI's local coordinates
+	return FindControlAtLocal(atx - X, aty - Y, leeway, must_be_clickable);
+}
+
+int32_t GUIMain::FindControlAtLocal(int atx, int aty, int leeway, bool must_be_clickable) const {
 	if (_G(loaded_game_file_version) <= kGameVersion_262) {
 		// Ignore draw order On 2.6.2 and lower
 		for (size_t i = 0; i < _controls.size(); ++i) {
@@ -99,7 +104,7 @@ int32_t GUIMain::FindControlUnderMouse(int leeway, bool must_be_clickable) const
 				continue;
 			if (!_controls[i]->IsClickable() && must_be_clickable)
 				continue;
-			if (_controls[i]->IsOverControl(_G(mousex), _G(mousey), leeway))
+			if (_controls[i]->IsOverControl(atx, aty, leeway))
 				return i;
 		}
 	} else {
@@ -109,21 +114,13 @@ int32_t GUIMain::FindControlUnderMouse(int leeway, bool must_be_clickable) const
 				continue;
 			if (!_controls[ctrl_index]->IsClickable() && must_be_clickable)
 				continue;
-			if (_controls[ctrl_index]->IsOverControl(_G(mousex), _G(mousey), leeway))
+			if (_controls[ctrl_index]->IsOverControl(atx, aty, leeway))
 				return ctrl_index;
 		}
 	}
 	return -1;
 }
 
-int32_t GUIMain::FindControlUnderMouse() const {
-	return FindControlUnderMouse(0, true);
-}
-
-int32_t GUIMain::FindControlUnderMouse(int leeway) const {
-	return FindControlUnderMouse(leeway, true);
-}
-
 int32_t GUIMain::GetControlCount() const {
 	return (int32_t)_controls.size();
 }
@@ -297,16 +294,13 @@ void GUIMain::DrawBlob(Bitmap *ds, int x, int y, color_t draw_color) {
 	ds->FillRect(Rect(x, y, x + get_fixed_pixel_size(1), y + get_fixed_pixel_size(1)), draw_color);
 }
 
-void GUIMain::Poll() {
-	int mxwas = _G(mousex), mywas = _G(mousey);
-
-	_G(mousex) -= X;
-	_G(mousey) -= Y;
-	if (_G(mousex) != MouseWasAt.X || _G(mousey) != MouseWasAt.Y) {
-		int ctrl_index = FindControlUnderMouse();
+void GUIMain::Poll(int mx, int my) {
+	mx -= X, my -= Y; // translate to GUI's local coordinates
+	if (mx != MouseWasAt.X || my != MouseWasAt.Y) {
+		int ctrl_index = FindControlAtLocal(mx, my, 0, false);
 
 		if (MouseOverCtrl == MOVER_MOUSEDOWNLOCKED)
-			_controls[MouseDownCtrl]->OnMouseMove(_G(mousex), _G(mousey));
+			_controls[MouseDownCtrl]->OnMouseMove(mx, my);
 		else if (ctrl_index != MouseOverCtrl) {
 			if (MouseOverCtrl >= 0)
 				_controls[MouseOverCtrl]->OnMouseLeave();
@@ -322,18 +316,16 @@ void GUIMain::Poll() {
 				MouseOverCtrl = ctrl_index;
 				if (MouseOverCtrl >= 0) {
 					_controls[MouseOverCtrl]->OnMouseEnter();
-					_controls[MouseOverCtrl]->OnMouseMove(_G(mousex), _G(mousey));
+					_controls[MouseOverCtrl]->OnMouseMove(mx, my);
 				}
 			}
 			//MarkChanged(); // TODO: only do if anything really changed
 		} else if (MouseOverCtrl >= 0)
-			_controls[MouseOverCtrl]->OnMouseMove(_G(mousex), _G(mousey));
+			_controls[MouseOverCtrl]->OnMouseMove(mx, my);
 	}
 
-	MouseWasAt.X = _G(mousex);
-	MouseWasAt.Y = _G(mousey);
-	_G(mousex) = mxwas;
-	_G(mousey) = mywas;
+	MouseWasAt.X = mx;
+	MouseWasAt.Y = my;
 }
 
 HError GUIMain::RebuildArray() {
@@ -459,19 +451,19 @@ void GUIMain::OnControlPositionChanged() {
 	MarkChanged();
 }
 
-void GUIMain::OnMouseButtonDown() {
+void GUIMain::OnMouseButtonDown(int mx, int my) {
 	if (MouseOverCtrl < 0)
 		return;
 
 	// don't activate disabled buttons
 	if (!IsGUIEnabled(_controls[MouseOverCtrl]) || !_controls[MouseOverCtrl]->IsVisible() ||
-	        !_controls[MouseOverCtrl]->IsClickable())
+		!_controls[MouseOverCtrl]->IsClickable())
 		return;
 
 	MouseDownCtrl = MouseOverCtrl;
 	if (_controls[MouseOverCtrl]->OnMouseDown())
 		MouseOverCtrl = MOVER_MOUSEDOWNLOCKED;
-	_controls[MouseDownCtrl]->OnMouseMove(_G(mousex) - X, _G(mousey) - Y);
+	_controls[MouseDownCtrl]->OnMouseMove(mx - X, my - Y);
 	//MarkChanged(); // TODO: only do if anything really changed
 }
 
diff --git a/engines/ags/shared/gui/gui_main.h b/engines/ags/shared/gui/gui_main.h
index c05611bbadc..cb97982e34a 100644
--- a/engines/ags/shared/gui/gui_main.h
+++ b/engines/ags/shared/gui/gui_main.h
@@ -105,11 +105,10 @@ public:
 	// Clears changed flag
 	void        ClearChanged();
 
-	int32_t FindControlUnderMouse() const;
-	// this version allows some extra leeway in the Editor so that
-	// the user can grab tiny controls
-	int32_t FindControlUnderMouse(int leeway) const;
-	int32_t FindControlUnderMouse(int leeway, bool must_be_clickable) const;
+	// Finds a control under given screen coordinates, returns control's child ID.
+	// Optionally allows extra leeway (offset in all directions) to let the user grab tiny controls.
+	// Optionally only allows clickable controls, ignoring non-clickable ones.
+	int32_t FindControlAt(int atx, int aty, int leeway = 0, bool must_be_clickable = true) const;
 	// Gets the number of the GUI child controls
 	int32_t GetControlCount() const;
 	// Gets control by its child's index
@@ -128,7 +127,8 @@ public:
 	bool    BringControlToFront(int index);
 	void    Draw(Bitmap *ds);
 	void    DrawAt(Bitmap *ds, int x, int y);
-	void    Poll();
+	// Polls GUI state, providing current cursor (mouse) coordinates
+	void    Poll(int mx, int my);
 	HError  RebuildArray();
 	void    ResortZOrder();
 	bool    SendControlToBack(int index);
@@ -147,7 +147,7 @@ public:
 	void    SetVisible(bool on);
 
 	// Events
-	void    OnMouseButtonDown();
+	void    OnMouseButtonDown(int mx, int my);
 	void    OnMouseButtonUp();
 	void    OnControlPositionChanged();
 
@@ -161,6 +161,8 @@ public:
 
 private:
 	void    DrawBlob(Bitmap *ds, int x, int y, color_t draw_color);
+	// Same as FindControlAt but expects local space coordinates
+	int32_t FindControlAtLocal(int atx, int aty, int leeway, bool must_be_clickable) const;
 
 	// TODO: all members are currently public; hide them later
 public:
@@ -189,7 +191,7 @@ public:
 
 	String  OnClickHandler; // script function name
 
-	private:
+private:
 	int32_t _flags;         // style and behavior flags
 	bool    _hasChanged;    // flag tells whether GUI has graphically changed recently
 


Commit: 17bac5b1efb0a239b3ef530474574609a9ff0fc0
    https://github.com/scummvm/scummvm/commit/17bac5b1efb0a239b3ef530474574609a9ff0fc0
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-04-23T18:15:02-07:00

Commit Message:
AGS: Removed redundant function domouse(int what)

>From upstream 3dfda41382bdd10e3b2b78f797fade8b5dcc1efa

Changed paths:
    engines/ags/engine/ac/dialog.cpp
    engines/ags/engine/ac/display.cpp
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/mouse.cpp
    engines/ags/engine/ac/mouse.h
    engines/ags/engine/ac/sys_events.cpp
    engines/ags/engine/ac/sys_events.h
    engines/ags/engine/device/mouse_w32.cpp
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/engine/ac/dialog.cpp b/engines/ags/engine/ac/dialog.cpp
index a55597b528b..0d317c807c2 100644
--- a/engines/ags/engine/ac/dialog.cpp
+++ b/engines/ags/engine/ac/dialog.cpp
@@ -590,14 +590,14 @@ void DialogOptions::Show() {
 
 	update_polled_stuff_if_runtime();
 	if (!_GP(play).mouse_cursor_hidden)
-		ags_domouse(DOMOUSE_ENABLE);
+		ags_domouse();
 	update_polled_stuff_if_runtime();
 
 	Redraw();
 	while (Run() && !SHOULD_QUIT) {}
 
 	if (!_GP(play).mouse_cursor_hidden)
-		ags_domouse(DOMOUSE_DISABLE);
+		ags_domouse();
 }
 
 void DialogOptions::Redraw() {
diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index c39ffb55db7..7961649ea13 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -272,7 +272,7 @@ int _display_main(int xx, int yy, int wii, const char *text, int disp_type, int
 		}
 
 		if (!_GP(play).mouse_cursor_hidden)
-			ags_domouse(DOMOUSE_ENABLE);
+			ags_domouse();
 		int countdown = GetTextDisplayTime(todis);
 		int skip_setting = user_to_internal_skip_speech((SkipSpeechStyle)_GP(play).skip_display);
 		// Loop until skipped
@@ -333,7 +333,7 @@ int _display_main(int xx, int yy, int wii, const char *text, int disp_type, int
 				break;
 		}
 		if (!_GP(play).mouse_cursor_hidden)
-			ags_domouse(DOMOUSE_DISABLE);
+			ags_domouse();
 		remove_screen_overlay(OVER_TEXTMSG);
 		invalidate_screen();
 	} else {
diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index d27c01677b8..2956681710b 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -2184,7 +2184,7 @@ void construct_game_screen_overlay(bool draw_mouse) {
 	// to the update loop instead of doing it in the drawing routine
 	// update animating mouse cursor
 	if (_GP(game).mcurs[_G(cur_cursor)].view >= 0) {
-		ags_domouse(DOMOUSE_NOCURSOR);
+		ags_domouse();
 		// only on mousemove, and it's not moving
 		if (((_GP(game).mcurs[_G(cur_cursor)].flags & MCF_ANIMMOVE) != 0) &&
 		        (_G(mousex) == _G(lastmx)) && (_G(mousey) == _G(lastmy)));
@@ -2212,7 +2212,7 @@ void construct_game_screen_overlay(bool draw_mouse) {
 		_G(lastmy) = _G(mousey);
 	}
 
-	ags_domouse(DOMOUSE_NOCURSOR);
+	ags_domouse();
 
 	// Stage: mouse cursor
 	if (draw_mouse && !_GP(play).mouse_cursor_hidden && _GP(play).screen_is_faded_out == 0) {
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index f94b2a25e32..4baae5d202f 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -197,14 +197,14 @@ void setup_for_dialog() {
 	_G(cbuttfont) = _GP(play).normal_font;
 	_G(acdialog_font) = _GP(play).normal_font;
 	if (!_GP(play).mouse_cursor_hidden)
-		ags_domouse(DOMOUSE_ENABLE);
+		ags_domouse();
 	_G(oldmouse) = _G(cur_cursor);
 	set_mouse_cursor(CURS_ARROW);
 }
 void restore_after_dialog() {
 	set_mouse_cursor(_G(oldmouse));
 	if (!_GP(play).mouse_cursor_hidden)
-		ags_domouse(DOMOUSE_DISABLE);
+		ags_domouse();
 	invalidate_screen();
 }
 
diff --git a/engines/ags/engine/ac/mouse.cpp b/engines/ags/engine/ac/mouse.cpp
index 8e5edbf4bf5..b1c47310938 100644
--- a/engines/ags/engine/ac/mouse.cpp
+++ b/engines/ags/engine/ac/mouse.cpp
@@ -270,7 +270,7 @@ void disable_cursor_mode(int modd) {
 }
 
 void RefreshMouse() {
-	ags_domouse(DOMOUSE_NOCURSOR);
+	ags_domouse();
 	_GP(scmouse).x = game_to_data_coord(_G(mousex));
 	_GP(scmouse).y = game_to_data_coord(_G(mousey));
 }
diff --git a/engines/ags/engine/ac/mouse.h b/engines/ags/engine/ac/mouse.h
index e6801cbc3d9..b9bd5aaaa81 100644
--- a/engines/ags/engine/ac/mouse.h
+++ b/engines/ags/engine/ac/mouse.h
@@ -26,11 +26,6 @@
 
 namespace AGS3 {
 
-#define DOMOUSE_UPDATE 0
-#define DOMOUSE_ENABLE 1
-#define DOMOUSE_DISABLE 2
-#define DOMOUSE_NOCURSOR 5
-
 void Mouse_SetVisible(int isOn);
 int Mouse_GetVisible();
 int Mouse_GetModeGraphic(int curs);
diff --git a/engines/ags/engine/ac/sys_events.cpp b/engines/ags/engine/ac/sys_events.cpp
index c0e2522b1d1..38ffc2549ed 100644
--- a/engines/ags/engine/ac/sys_events.cpp
+++ b/engines/ags/engine/ac/sys_events.cpp
@@ -208,12 +208,8 @@ void ags_mouse_get_relxy(int &x, int &y) {
 	_G(mouse_accum_rely) = 0;
 }
 
-void ags_domouse(int what) {
-	// do mouse is "update the mouse x,y and also the cursor position", unless DOMOUSE_NOCURSOR is set.
-	if (what == DOMOUSE_NOCURSOR)
-		mgetgraphpos();
-	else
-		domouse(what);
+void ags_domouse() {
+	mgetgraphpos();
 }
 
 int ags_check_mouse_wheel() {
diff --git a/engines/ags/engine/ac/sys_events.h b/engines/ags/engine/ac/sys_events.h
index 7313b641bf9..e1fb98bc3c6 100644
--- a/engines/ags/engine/ac/sys_events.h
+++ b/engines/ags/engine/ac/sys_events.h
@@ -91,7 +91,7 @@ extern int  ags_mgetbutton();
 // Returns recent relative mouse movement
 extern void ags_mouse_get_relxy(int &x, int &y);
 // Updates mouse cursor position in game
-extern void ags_domouse(int what);
+extern void ags_domouse();
 // Returns -1 for wheel down and +1 for wheel up
 // TODO: introduce constants for this
 extern int  ags_check_mouse_wheel();
diff --git a/engines/ags/engine/device/mouse_w32.cpp b/engines/ags/engine/device/mouse_w32.cpp
index e4ef81db0db..229d93b0ab2 100644
--- a/engines/ags/engine/device/mouse_w32.cpp
+++ b/engines/ags/engine/device/mouse_w32.cpp
@@ -100,28 +100,6 @@ void msetcursorlimit(int x1, int y1, int x2, int y2) {
 	_G(boundy2) = y2;
 }
 
-void domouse(int str) {
-	int poow = _G(mousecurs)[(int)_G(currentcursor)]->GetWidth();
-	int pooh = _G(mousecurs)[(int)_G(currentcursor)]->GetHeight();
-	//int smx = _G(mousex) - _G(hotxwas), smy = _G(mousey) - _G(hotywas);
-	const Rect &viewport = _GP(play).GetMainViewport();
-
-	mgetgraphpos();
-	_G(mousex) -= _G(hotx);
-	_G(mousey) -= _G(hoty);
-
-	if (_G(mousex) + poow >= viewport.GetWidth())
-		poow = viewport.GetWidth() - _G(mousex);
-
-	if (_G(mousey) + pooh >= viewport.GetHeight())
-		pooh = viewport.GetHeight() - _G(mousey);
-
-	_G(mousex) += _G(hotx);
-	_G(mousey) += _G(hoty);
-	_G(hotxwas) = _G(hotx);
-	_G(hotywas) = _G(hoty);
-}
-
 void msetgraphpos(int xa, int ya) {
 	_G(real_mouse_x) = xa;
 	_G(real_mouse_y) = ya;
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 007a96302a1..4bc4e1c1526 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -44,6 +44,7 @@
 #include "ags/engine/ac/path_helper.h"
 #include "ags/engine/ac/room_status.h"
 #include "ags/engine/ac/string.h"
+#include "ags/engine/ac/sys_events.h"
 #include "ags/shared/ac/sprite_cache.h"
 #include "ags/engine/ac/dynobj/cc_dynamic_object_addr_and_manager.h"
 #include "ags/engine/ac/dynobj/script_string.h"
@@ -340,10 +341,8 @@ void IAGSEngine::BlitSpriteRotated(int32 x, int32 y, BITMAP *bmp, int32 angle) {
 	rotate_sprite(ds->GetAllegroBitmap(), bmp, x, y, itofix(angle));
 }
 
-extern void domouse(int);
-
 void IAGSEngine::PollSystem() {
-	domouse(DOMOUSE_NOCURSOR);
+	ags_domouse();
 	update_polled_stuff_if_runtime();
 	int mbut, mwheelz;
 	if (run_service_mb_controls(mbut, mwheelz) && mbut >= 0 && !_GP(play).IsIgnoringInput())


Commit: 35e6c82ac8da8d4301148dbd00de2121437214b3
    https://github.com/scummvm/scummvm/commit/35e6c82ac8da8d4301148dbd00de2121437214b3
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-04-23T18:56:44-07:00

Commit Message:
AGS: Don't use global loadSaveGameOnStartup beyond starting function

>From upstream 4bf3b328b2dd6f0a46a85bf501aded17a93f4a51

Changed paths:
    engines/ags/engine/main/game_start.cpp
    engines/ags/engine/main/game_start.h


diff --git a/engines/ags/engine/main/game_start.cpp b/engines/ags/engine/main/game_start.cpp
index a8f6aa28a7b..a5872459a2d 100644
--- a/engines/ags/engine/main/game_start.cpp
+++ b/engines/ags/engine/main/game_start.cpp
@@ -65,10 +65,10 @@ void start_game_init_editor_debugging() {
 	}
 }
 
-void start_game_load_savegame_on_startup() {
-	if (_G(loadSaveGameOnStartup) != -1) {
+static void start_game_load_savegame_on_startup(int loadSave) {
+	if (loadSave != -1) {
 		current_fade_out_effect();
-		try_restore_save(_G(loadSaveGameOnStartup));
+		try_restore_save(loadSave);
 	}
 }
 
@@ -99,7 +99,7 @@ void start_game() {
 	first_room_initialization();
 }
 
-void initialize_start_and_play_game(int override_start_room, int loadSaveGameOnStartup) {
+void initialize_start_and_play_game(int override_start_room, int loadSave) {
 	//try { // BEGIN try for ALI3DEXception
 
 	set_cursor_mode(MODE_WALK);
@@ -113,7 +113,7 @@ void initialize_start_and_play_game(int override_start_room, int loadSaveGameOnS
 
 	start_game_init_editor_debugging();
 
-	start_game_load_savegame_on_startup();
+	start_game_load_savegame_on_startup(loadSave);
 
 	// only start if not restored a save
 	if (_G(displayed_room) < 0)
diff --git a/engines/ags/engine/main/game_start.h b/engines/ags/engine/main/game_start.h
index afff73f9775..f8d97ba18e5 100644
--- a/engines/ags/engine/main/game_start.h
+++ b/engines/ags/engine/main/game_start.h
@@ -25,7 +25,7 @@
 namespace AGS3 {
 
 extern void start_game();
-extern void initialize_start_and_play_game(int override_start_room, int loadSaveGameOnStartup);
+extern void initialize_start_and_play_game(int override_start_room, int loadSave);
 
 } // namespace AGS3
 


Commit: 1d381748818410463b073ae15a16b0fa1a88c5b4
    https://github.com/scummvm/scummvm/commit/1d381748818410463b073ae15a16b0fa1a88c5b4
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-04-23T19:04:31-07:00

Commit Message:
AGS: Fixed new SpriteFileWriter saving of 16-bit sprites w palette

>From upstream 972145a759c9a590cdf60197668191790b474000

Changed paths:
    engines/ags/shared/ac/sprite_file.cpp


diff --git a/engines/ags/shared/ac/sprite_file.cpp b/engines/ags/shared/ac/sprite_file.cpp
index e1472a22bdb..39363fcb9a8 100644
--- a/engines/ags/shared/ac/sprite_file.cpp
+++ b/engines/ags/shared/ac/sprite_file.cpp
@@ -116,7 +116,7 @@ static void UnpackIndexedBitmap(Bitmap *image, const uint8_t *data, size_t data_
 		switch (bpp) {
 		case 2: *((uint16_t *)dst) = color; break;
 		case 4: *((uint32_t *)dst) = color; break;
-		default: assert(0); break;
+		default: assert(0); return;
 		}
 	}
 }
@@ -373,8 +373,14 @@ HError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
 	uint32_t pal_bpp = GetPaletteBPP(hdr.SFormat);
 	if (pal_bpp > 0) { // read palette if format assumes one
 		switch (pal_bpp) {
-		case 2: for (uint32_t i = 0; i < hdr.PalCount; ++i) palette[i] = _stream->ReadInt16(); break;
-		case 4: for (uint32_t i = 0; i < hdr.PalCount; ++i) palette[i] = _stream->ReadInt32(); break;
+		case 2: for (uint32_t i = 0; i < hdr.PalCount; ++i) {
+			palette[i] = _stream->ReadInt16();
+		}
+			  break;
+		case 4: for (uint32_t i = 0; i < hdr.PalCount; ++i) {
+			palette[i] = _stream->ReadInt32();
+		}
+			  break;
 		default: assert(0); break;
 		}
 		indexed_buf.resize(w * h);
@@ -390,21 +396,26 @@ HError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
 			return new Error(String::FromFormat("LoadSprite: bad compressed data for sprite %d.", index));
 		}
 		switch (hdr.Compress) {
-		case kSprCompress_RLE: rle_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get()); break;
-		case kSprCompress_LZW: lzw_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get()); break;
-		default: assert(!"Unsupported compression type!");
+		case kSprCompress_RLE: rle_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get());
+			break;
+		case kSprCompress_LZW: lzw_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get());
+			break;
+		default: assert(!"Unsupported compression type!"); break;
 		}
 		// TODO: test that not more than data_size was read!
 	}
 	// Otherwise (no compression) read directly
 	else {
 		switch (im_data.BPP) {
-		case 1: _stream->Read(im_data.Buf, im_data.Size); break;
+		case 1: _stream->Read(im_data.Buf, im_data.Size);
+			break;
 		case 2: _stream->ReadArrayOfInt16(
-			reinterpret_cast<int16_t *>(im_data.Buf), im_data.Size / sizeof(int16_t)); break;
+			reinterpret_cast<int16_t *>(im_data.Buf), im_data.Size / sizeof(int16_t));
+			break;
 		case 4: _stream->ReadArrayOfInt32(
-			reinterpret_cast<int32_t *>(im_data.Buf), im_data.Size / sizeof(int32_t)); break;
-		default: assert(0);
+			reinterpret_cast<int32_t *>(im_data.Buf), im_data.Size / sizeof(int32_t));
+			break;
+		default: assert(0); break;
 		}
 	}
 	// Finally revert storage options
@@ -611,9 +622,11 @@ void SpriteFileWriter::WriteBitmap(Bitmap *image) {
 		compress = _compress;
 		VectorStream mems(_membuf, kStream_Write);
 		switch (compress) {
-		case kSprCompress_RLE: rle_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems); break;
-		case kSprCompress_LZW: lzw_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems); break;
-		default: assert(!"Unsupported compression type!");
+		case kSprCompress_RLE: rle_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
+			break;
+		case kSprCompress_LZW: lzw_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
+			break;
+		default: assert(!"Unsupported compression type!"); break;
 		}
 		// mark to write as a plain byte array
 		im_data = ImBufferCPtr(&_membuf[0], _membuf.size(), 1);
@@ -647,20 +660,27 @@ void SpriteFileWriter::WriteSpriteData(const SpriteDatHeader &hdr,
 	if (pal_bpp > 0) {
 		assert(hdr.PalCount > 0);
 		switch (pal_bpp) {
-		case 1: break;
-		case 2: for (uint32_t i = 0; i < hdr.PalCount; ++i) _out->WriteInt16(palette[i]); break;
-		case 4: for (uint32_t i = 0; i < hdr.PalCount; ++i) _out->WriteInt32(palette[i]); break;
-		default: assert(0); break;
+		case 2: for (uint32_t i = 0; i < hdr.PalCount; ++i) {
+			_out->WriteInt16(palette[i]);
+		}
+			  break;
+		case 4: for (uint32_t i = 0; i < hdr.PalCount; ++i) {
+			_out->WriteInt32(palette[i]);
+		}
+			  break;
 		}
 	}
 	// write the image pixel data
 	_out->WriteInt32(im_data_sz);
 	switch (im_bpp) {
-	case 1: _out->Write(im_data, im_data_sz); break;
+	case 1: _out->Write(im_data, im_data_sz);
+		break;
 	case 2: _out->WriteArrayOfInt16(reinterpret_cast<const int16_t *>(im_data),
-		im_data_sz / sizeof(int16_t)); break;
+		im_data_sz / sizeof(int16_t));
+		break;
 	case 4: _out->WriteArrayOfInt32(reinterpret_cast<const int32_t *>(im_data),
-		im_data_sz / sizeof(int32_t)); break;
+		im_data_sz / sizeof(int32_t));
+		break;
 	default: assert(0); break;
 	}
 }


Commit: 1f666cf0dc87bada06f7c65428b80b554abc7f79
    https://github.com/scummvm/scummvm/commit/1f666cf0dc87bada06f7c65428b80b554abc7f79
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-04-23T19:26:25-07:00

Commit Message:
AGS: Fixed loading a different game when restoring a save

>From upstream 56fb3029f61706b655f778349e2c4f50c8e03427

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/main/game_file.cpp
    engines/ags/shared/core/asset_manager.h
    engines/ags/shared/game/main_game_file.cpp
    engines/ags/shared/game/main_game_file.h


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 4baae5d202f..7caf2911b3c 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -955,9 +955,11 @@ bool read_savedgame_screenshot(const String &savedgame, int &want_shot) {
 
 // Test if the game file contains expected GUID / legacy id
 bool test_game_guid(const String &filepath, const String &guid, int legacy_id) {
+	std::unique_ptr<AssetManager> amgr(new AssetManager());
+	if (amgr->AddLibrary(filepath) != kAssetNoError)
+		return false;
 	MainGameSource src;
-	HGameFileError err = OpenMainGameFileFromDefaultAsset(src);
-	if (!err)
+	if (!OpenMainGameFileFromDefaultAsset(src, amgr.get()))
 		return false;
 	GameSetupStruct g;
 	PreReadGameData(g, src.InputStream.get(), src.DataVersion);
@@ -999,7 +1001,7 @@ HSaveError load_game(const String &path, int slotNumber, bool &data_overwritten)
 			String gamefile = FindGameData(_GP(ResPaths).DataDir, TestGame);
 
 			if (Shared::File::TestReadFile(gamefile)) {
-				RunAGSGame(desc.MainDataFilename, 0, 0);
+				RunAGSGame(gamefile.GetCStr(), 0, 0);
 				_G(load_new_game_restore) = slotNumber;
 				return HSaveError::None();
 			}
diff --git a/engines/ags/engine/main/game_file.cpp b/engines/ags/engine/main/game_file.cpp
index 25a9ad47e3c..3fe3ceedf8c 100644
--- a/engines/ags/engine/main/game_file.cpp
+++ b/engines/ags/engine/main/game_file.cpp
@@ -77,7 +77,7 @@ String get_caps_list(const std::set<String> &caps) {
 // Called when the game file is opened for the first time (when preloading game data);
 // it logs information on data version and reports first found errors, if any.
 HGameFileError game_file_first_open(MainGameSource &src) {
-	HGameFileError err = OpenMainGameFileFromDefaultAsset(src);
+	HGameFileError err = OpenMainGameFileFromDefaultAsset(src, _G(AssetMgr)->get());
 	if (err ||
 	        err->Code() == kMGFErr_SignatureFailed ||
 	        err->Code() == kMGFErr_FormatVersionTooOld ||
@@ -171,7 +171,7 @@ HError LoadGameScripts(LoadedGameEntities &ents, GameDataVersion data_ver) {
 HError load_game_file() {
 	MainGameSource src;
 	LoadedGameEntities ents(_GP(game), _G(dialog));
-	HError err = (HError)OpenMainGameFileFromDefaultAsset(src);
+	HError err = (HError)OpenMainGameFileFromDefaultAsset(src, _GP(AssetMgr).get());
 	if (err) {
 		err = (HError)ReadGameData(ents, src.InputStream.get(), src.DataVersion);
 		src.InputStream.reset();
diff --git a/engines/ags/shared/core/asset_manager.h b/engines/ags/shared/core/asset_manager.h
index a61dd606c20..0655d0793c2 100644
--- a/engines/ags/shared/core/asset_manager.h
+++ b/engines/ags/shared/core/asset_manager.h
@@ -139,7 +139,7 @@ private:
 	// Loads library and registers its contents into the cache
 	AssetError  RegisterAssetLib(const String &path, AssetLibEx *&lib);
 
-	// Tries to find asset in known locations, tests if it's possible to open, and fills in AssetLocation
+	// Tries to find asset in the given location, and then opens a stream for reading
 	Stream *OpenAssetFromLib(const AssetLibEx *lib, const String &asset_name) const;
 	Stream *OpenAssetFromDir(const AssetLibEx *lib, const String &asset_name) const;
 
diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index df1bf538bfb..f93c0aa1ff4 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -199,15 +199,15 @@ HGameFileError OpenMainGameFile(const String &filename, MainGameSource &src) {
 	return OpenMainGameFileBase(in, src);
 }
 
-HGameFileError OpenMainGameFileFromDefaultAsset(MainGameSource &src) {
+HGameFileError OpenMainGameFileFromDefaultAsset(MainGameSource &src, AssetManager *mgr) {
 	// Cleanup source struct
 	src = MainGameSource();
 	// Try to find and open main game file
 	String filename = MainGameSource::DefaultFilename_v3;
-	Stream *in = _GP(AssetMgr)->OpenAsset(filename);
+	Stream *in = mgr->OpenAsset(filename);
 	if (!in) {
 		filename = MainGameSource::DefaultFilename_v2;
-		in = _GP(AssetMgr)->OpenAsset(filename);
+		in = mgr->OpenAsset(filename);
 	}
 	if (!in)
 		return new MainGameFileError(kMGFErr_FileOpenFailed, String::FromFormat("Filename: %s.", filename.GetCStr()));
diff --git a/engines/ags/shared/game/main_game_file.h b/engines/ags/shared/game/main_game_file.h
index fe3b9e0ef1e..35aeb68ef6a 100644
--- a/engines/ags/shared/game/main_game_file.h
+++ b/engines/ags/shared/game/main_game_file.h
@@ -139,6 +139,8 @@ struct LoadedGameEntities {
 	~LoadedGameEntities();
 };
 
+class AssetManager;
+
 // Tells if the given path (library filename) contains main game file
 bool               IsMainGameLibrary(const String &filename);
 // Scans given directory path for a package containing main game data, returns first found or none.
@@ -146,8 +148,8 @@ String             FindGameData(const String &path);
 String             FindGameData(const String &path, bool(*fn_testfile)(const String &));
 // Opens main game file for reading from an arbitrary file
 HGameFileError     OpenMainGameFile(const String &filename, MainGameSource &src);
-// Opens main game file for reading from the asset library (uses default asset name)
-HGameFileError     OpenMainGameFileFromDefaultAsset(MainGameSource &src);
+// Opens main game file for reading using the current Asset Manager (uses default asset name)
+HGameFileError     OpenMainGameFileFromDefaultAsset(MainGameSource &src, AssetManager *mgr);
 // Reads game data, applies necessary conversions to match current format version
 HGameFileError     ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersion data_ver);
 // Pre-reads the heading game data, just enough to identify the game and its special file locations


Commit: 9dd7ce9313fca6816c1c4c4cbb47aa36ec0d9446
    https://github.com/scummvm/scummvm/commit/9dd7ce9313fca6816c1c4c4cbb47aa36ec0d9446
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-04-23T19:36:22-07:00

Commit Message:
AGS: Store debug room names in std::vector

>From upstream 83499b1afbb1a4c0717cf9b665969f4e4ca9b6b1

Changed paths:
    engines/ags/engine/gui/gui_dialog.cpp
    engines/ags/engine/gui/gui_dialog.h
    engines/ags/shared/ac/game_setup_struct.cpp
    engines/ags/shared/ac/game_setup_struct.h


diff --git a/engines/ags/engine/gui/gui_dialog.cpp b/engines/ags/engine/gui/gui_dialog.cpp
index 665d943096d..761c6f3981e 100644
--- a/engines/ags/engine/gui/gui_dialog.cpp
+++ b/engines/ags/engine/gui/gui_dialog.cpp
@@ -342,7 +342,8 @@ int enternumberwindow(char *prompttext) {
 	return atoi(ourbuf);
 }
 
-int roomSelectorWindow(int currentRoom, int numRooms, int *roomNumbers, char **roomNames) {
+int roomSelectorWindow(int currentRoom, int numRooms,
+		const std::vector<int> &roomNumbers, const std::vector<String> &roomNames) {
 	char labeltext[200];
 	strcpy(labeltext, get_global_message(MSG_SAVEDIALOG));
 	const int wnd_width = 240;
@@ -358,7 +359,7 @@ int roomSelectorWindow(int currentRoom, int numRooms, int *roomNumbers, char **r
 
 	CSCISendControlMessage(ctrllist, CLB_CLEAR, 0, 0);    // clear the list box
 	for (int aa = 0; aa < numRooms; aa++) {
-		sprintf(_G(buff), "%3d %s", roomNumbers[aa], roomNames[aa]);
+		snprintf(_G(buff), sizeof(_G(buff)), "%3d %s", roomNumbers[aa], roomNames[aa].GetCStr());
 		CSCISendControlMessage(ctrllist, CLB_ADDITEM, 0, &_G(buff)[0]);
 		if (roomNumbers[aa] == currentRoom) {
 			CSCISendControlMessage(ctrllist, CLB_SETCURSEL, aa, 0);
diff --git a/engines/ags/engine/gui/gui_dialog.h b/engines/ags/engine/gui/gui_dialog.h
index 9332caa022a..4e3a1d67310 100644
--- a/engines/ags/engine/gui/gui_dialog.h
+++ b/engines/ags/engine/gui/gui_dialog.h
@@ -19,8 +19,11 @@
  *
  */
 
-#ifndef AGS_ENGINE_GUI__GUIDIALOG_H
-#define AGS_ENGINE_GUI__GUIDIALOG_H
+#ifndef AGS_ENGINE_GUI_GUI_DIALOG_H
+#define AGS_ENGINE_GUI_GUI_DIALOG_H
+
+#include "ags/lib/std/vector.h"
+#include "ags/shared/util/string.h"
 
 namespace AGS3 {
 
@@ -44,7 +47,8 @@ int  savegamedialog();
 void preparesavegamelist(int ctrllist);
 void enterstringwindow(const char *prompttext, char *stouse);
 int  enternumberwindow(char *prompttext);
-int  roomSelectorWindow(int currentRoom, int numRooms, int *roomNumbers, char **roomNames);
+int  roomSelectorWindow(int currentRoom, int numRooms,
+	const std::vector<int> &roomNumbers, const std::vector<AGS::Shared::String> &roomNames);
 int  myscimessagebox(const char *lpprompt, char *btn1, char *btn2);
 int  quitdialog();
 
diff --git a/engines/ags/shared/ac/game_setup_struct.cpp b/engines/ags/shared/ac/game_setup_struct.cpp
index 042a3dd2a69..615eccd2267 100644
--- a/engines/ags/shared/ac/game_setup_struct.cpp
+++ b/engines/ags/shared/ac/game_setup_struct.cpp
@@ -35,8 +35,6 @@ using namespace AGS::Shared;
 GameSetupStruct::GameSetupStruct()
 	: filever(0)
 	, roomCount(0)
-	, roomNumbers(nullptr)
-	, roomNames(nullptr)
 	, scoreClipID(0) {
 	memset(invinfo, 0, sizeof(invinfo));
 	memset(lipSyncFrameLetters, 0, sizeof(lipSyncFrameLetters));
@@ -62,10 +60,8 @@ void GameSetupStruct::Free() {
 	invScripts.clear();
 	numinvitems = 0;
 
-	for (int i = 0; i < roomCount; i++)
-		delete roomNames[i];
-	delete[] roomNames;
-	delete[] roomNumbers;
+	roomNames.clear();
+	roomNumbers.clear();
 	roomCount = 0;
 
 	audioClips.clear();
@@ -324,14 +320,11 @@ HGameFileError GameSetupStruct::read_audio(Shared::Stream *in, GameDataVersion d
 void GameSetupStruct::read_room_names(Stream *in, GameDataVersion data_ver) {
 	if ((data_ver >= kGameVersion_301) && (options[OPT_DEBUGMODE] != 0)) {
 		roomCount = in->ReadInt32();
-		roomNumbers = new int[roomCount];
-		roomNames = new char *[roomCount];
-		String pexbuf;
-		for (int bb = 0; bb < roomCount; bb++) {
-			roomNumbers[bb] = in->ReadInt32();
-			pexbuf.Read(in, STD_BUFFER_SIZE);
-			roomNames[bb] = new char[pexbuf.GetLength() + 1];
-			strcpy(roomNames[bb], pexbuf.GetCStr());
+		roomNumbers.resize(roomCount);
+		roomNames.resize(roomCount);
+		for (int i = 0; i < roomCount; ++i) {
+			roomNumbers[i] = in->ReadInt32();
+			roomNames[i].Read(in);
 		}
 	} else {
 		roomCount = 0;
diff --git a/engines/ags/shared/ac/game_setup_struct.h b/engines/ags/shared/ac/game_setup_struct.h
index 7ae5da14ecd..3d48ec212d3 100644
--- a/engines/ags/shared/ac/game_setup_struct.h
+++ b/engines/ags/shared/ac/game_setup_struct.h
@@ -81,8 +81,8 @@ struct GameSetupStruct : public GameSetupStructBase {
 	// NOTE: saveGameFolderName is generally used to create game subdirs in common user directories
 	char              saveGameFolderName[MAX_SG_FOLDER_LEN];
 	int               roomCount;
-	int *roomNumbers;
-	char **roomNames;
+	std::vector<int>  roomNumbers;
+	std::vector<Shared::String> roomNames;
 	std::vector<ScriptAudioClip> audioClips;
 	std::vector<AudioClipType> audioClipTypes;
 	// A clip to play when player gains score in game


Commit: 51a0c34352fc33c8e65b60e01674ffe3e9d9cf3f
    https://github.com/scummvm/scummvm/commit/51a0c34352fc33c8e65b60e01674ffe3e9d9cf3f
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-04-23T21:13:12-07:00

Commit Message:
AGS: Serialize dynamic objects using MemoryStream

>From upstream ccee37e303af2d9e6ca23b115ff33631ca1244af

Changed paths:
    engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
    engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
    engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
    engines/ags/engine/ac/dynobj/cc_audio_channel.h
    engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
    engines/ags/engine/ac/dynobj/cc_audio_clip.h
    engines/ags/engine/ac/dynobj/cc_character.cpp
    engines/ags/engine/ac/dynobj/cc_character.h
    engines/ags/engine/ac/dynobj/cc_dialog.cpp
    engines/ags/engine/ac/dynobj/cc_dialog.h
    engines/ags/engine/ac/dynobj/cc_gui.cpp
    engines/ags/engine/ac/dynobj/cc_gui.h
    engines/ags/engine/ac/dynobj/cc_gui_object.cpp
    engines/ags/engine/ac/dynobj/cc_gui_object.h
    engines/ags/engine/ac/dynobj/cc_hotspot.cpp
    engines/ags/engine/ac/dynobj/cc_hotspot.h
    engines/ags/engine/ac/dynobj/cc_inventory.cpp
    engines/ags/engine/ac/dynobj/cc_inventory.h
    engines/ags/engine/ac/dynobj/cc_object.cpp
    engines/ags/engine/ac/dynobj/cc_object.h
    engines/ags/engine/ac/dynobj/cc_region.cpp
    engines/ags/engine/ac/dynobj/cc_region.h
    engines/ags/engine/ac/dynobj/script_camera.cpp
    engines/ags/engine/ac/dynobj/script_camera.h
    engines/ags/engine/ac/dynobj/script_date_time.cpp
    engines/ags/engine/ac/dynobj/script_date_time.h
    engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
    engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
    engines/ags/engine/ac/dynobj/script_dict.cpp
    engines/ags/engine/ac/dynobj/script_dict.h
    engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
    engines/ags/engine/ac/dynobj/script_drawing_surface.h
    engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
    engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
    engines/ags/engine/ac/dynobj/script_overlay.cpp
    engines/ags/engine/ac/dynobj/script_overlay.h
    engines/ags/engine/ac/dynobj/script_set.cpp
    engines/ags/engine/ac/dynobj/script_set.h
    engines/ags/engine/ac/dynobj/script_string.cpp
    engines/ags/engine/ac/dynobj/script_string.h
    engines/ags/engine/ac/dynobj/script_view_frame.cpp
    engines/ags/engine/ac/dynobj/script_view_frame.h
    engines/ags/engine/ac/dynobj/script_viewport.cpp
    engines/ags/engine/ac/dynobj/script_viewport.h


diff --git a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
index 45a980b139b..285ee571331 100644
--- a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
@@ -19,11 +19,10 @@
  *
  */
 
-//include <string.h>
 #include "ags/shared/core/types.h"
 #include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
 #include "ags/shared/ac/common.h"               // quit()
-#include "ags/shared/util/bbop.h"
+#include "ags/shared/util/memory_stream.h"
 
 namespace AGS3 {
 
@@ -36,27 +35,16 @@ int AGSCCDynamicObject::Dispose(const char *address, bool force) {
 	return 0;
 }
 
-void AGSCCDynamicObject::StartSerialize(char *sbuffer) {
-	bytesSoFar = 0;
-	serbuffer = sbuffer;
-}
-
-void AGSCCDynamicObject::SerializeInt(int val) {
-	char *chptr = &serbuffer[bytesSoFar];
-	int *iptr = (int *)chptr;
-	*iptr = BBOp::Int32FromLE(val);
-	bytesSoFar += 4;
-}
-
-void AGSCCDynamicObject::SerializeFloat(float val) {
-	char *chptr = &serbuffer[bytesSoFar];
-	float *fptr = (float *)chptr;
-	*fptr = BBOp::FloatFromLE(val);
-	bytesSoFar += 4;
-}
+int AGSCCDynamicObject::Serialize(const char *address, char *buffer, int bufsize) {
+	// If the required space is larger than the provided buffer,
+	// then return negated required space, notifying the caller that a larger buffer is necessary
+	size_t req_size = CalcSerializeSize();
+	if (bufsize < 0 || req_size > static_cast<size_t>(bufsize))
+		return -(static_cast<int32_t>(req_size));
 
-int AGSCCDynamicObject::EndSerialize() {
-	return bytesSoFar;
+	MemoryStream mems(reinterpret_cast<uint8_t *>(buffer), bufsize, kStream_Write);
+	Serialize(address, &mems);
+	return static_cast<int32_t>(mems.GetPosition());
 }
 
 void AGSCCDynamicObject::StartUnserialize(const char *sbuffer, int pTotalBytes) {
@@ -66,7 +54,7 @@ void AGSCCDynamicObject::StartUnserialize(const char *sbuffer, int pTotalBytes)
 }
 
 int AGSCCDynamicObject::UnserializeInt() {
-	if (bytesSoFar >= totalBytes)
+	if (bytesSoFar >= totalBytes) // FIXME: don't use quit! return error instead
 		quit("Unserialise: internal error: read past EOF");
 
 	char *chptr = &serbuffer[bytesSoFar];
@@ -75,7 +63,7 @@ int AGSCCDynamicObject::UnserializeInt() {
 }
 
 float AGSCCDynamicObject::UnserializeFloat() {
-	if (bytesSoFar >= totalBytes)
+	if (bytesSoFar >= totalBytes) // FIXME: don't use quit! return error instead
 		quit("Unserialise: internal error: read past EOF");
 
 	char *chptr = &serbuffer[bytesSoFar];
diff --git a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
index e4a7449a074..b6912a16cfc 100644
--- a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
@@ -26,6 +26,8 @@
 
 namespace AGS3 {
 
+namespace AGS { namespace Shared { class Stream; } }
+
 struct AGSCCDynamicObject : ICCDynamicObject {
 protected:
 	virtual ~AGSCCDynamicObject() {}
@@ -34,6 +36,7 @@ public:
 	int Dispose(const char *address, bool force) override;
 
 	// TODO: pass savegame format version
+	int Serialize(const char *address, char *buffer, int bufsize) override;
 	virtual void Unserialize(int index, const char *serializedData, int dataSize) = 0;
 
 	// Legacy support for reading and writing object values by their relative offset
@@ -56,14 +59,13 @@ protected:
 	int totalBytes = 0;
 	char *serbuffer = nullptr;
 
-	void StartSerialize(char *sbuffer);
-	void SerializeInt(int val);
-	void SerializeFloat(float val);
-	int  EndSerialize();
+	// Calculate and return required space for serialization, in bytes
+	virtual size_t CalcSerializeSize() = 0;
+	// Write object data into the provided stream
+	virtual void Serialize(const char *address, AGS::Shared::Stream *out) = 0;
 	void StartUnserialize(const char *sbuffer, int pTotalBytes);
 	int  UnserializeInt();
 	float UnserializeFloat();
-
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp b/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
index d4758b2977e..6d9b2c71454 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "ags/shared/util/stream.h"
 #include "ags/engine/ac/dynobj/cc_audio_channel.h"
 #include "ags/engine/ac/dynobj/script_audio_channel.h"
 #include "ags/engine/media/audio/audio_system.h"
@@ -26,15 +27,19 @@
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 const char *CCAudioChannel::GetType() {
 	return "AudioChannel";
 }
 
-int CCAudioChannel::Serialize(const char *address, char *buffer, int bufsize) {
-	const ScriptAudioChannel *ach = (const ScriptAudioChannel *)address;
-	StartSerialize(buffer);
-	SerializeInt(ach->id);
-	return EndSerialize();
+size_t CCAudioChannel::CalcSerializeSize() {
+	return sizeof(int32_t);
+}
+
+void CCAudioChannel::Serialize(const char *address, Stream *out) {
+	ScriptAudioChannel *ach = (ScriptAudioChannel *)address;
+	out->WriteInt32(ach->id);
 }
 
 void CCAudioChannel::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_channel.h b/engines/ags/engine/ac/dynobj/cc_audio_channel.h
index 2025342548b..c8a250c68c9 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_channel.h
+++ b/engines/ags/engine/ac/dynobj/cc_audio_channel.h
@@ -19,8 +19,8 @@
  *
  */
 
-#ifndef AGS_ENGINE_DYNOBJ__CCAUDIOCHANNEL_H
-#define AGS_ENGINE_DYNOBJ__CCAUDIOCHANNEL_H
+#ifndef AGS_ENGINE_DYNOBJ_CC_AUDIO_CHANNEL_H
+#define AGS_ENGINE_DYNOBJ_CC_AUDIO_CHANNEL_H
 
 #include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
 
@@ -28,8 +28,12 @@ namespace AGS3 {
 
 struct CCAudioChannel final : AGSCCDynamicObject {
 	const char *GetType() override;
-	int Serialize(const char *address, char *buffer, int bufsize) override;
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp b/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
index c52b6d3fe61..353589433c5 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
@@ -22,20 +22,23 @@
 #include "ags/engine/ac/dynobj/cc_audio_clip.h"
 #include "ags/shared/ac/dynobj/script_audio_clip.h"
 #include "ags/shared/ac/game_setup_struct.h"
+#include "ags/shared/util/stream.h"
 
 namespace AGS3 {
 
-
+using namespace AGS::Shared;
 
 const char *CCAudioClip::GetType() {
 	return "AudioClip";
 }
 
-int CCAudioClip::Serialize(const char *address, char *buffer, int bufsize) {
-	const ScriptAudioClip *ach = (const ScriptAudioClip *)address;
-	StartSerialize(buffer);
-	SerializeInt(ach->id);
-	return EndSerialize();
+size_t CCAudioClip::CalcSerializeSize() {
+	return sizeof(int32_t);
+}
+
+void CCAudioClip::Serialize(const char *address, Stream *out) {
+	ScriptAudioClip *ach = (ScriptAudioClip *)address;
+	out->WriteInt32(ach->id);
 }
 
 void CCAudioClip::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_clip.h b/engines/ags/engine/ac/dynobj/cc_audio_clip.h
index 2eea6e36fa3..d4126ec7fef 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_clip.h
+++ b/engines/ags/engine/ac/dynobj/cc_audio_clip.h
@@ -28,8 +28,12 @@ namespace AGS3 {
 
 struct CCAudioClip final : AGSCCDynamicObject {
 	const char *GetType() override;
-	int Serialize(const char *address, char *buffer, int bufsize) override;
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_character.cpp b/engines/ags/engine/ac/dynobj/cc_character.cpp
index 8fc13c081c0..7f9fd3ac216 100644
--- a/engines/ags/engine/ac/dynobj/cc_character.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_character.cpp
@@ -24,11 +24,12 @@
 #include "ags/engine/ac/global_character.h"
 #include "ags/shared/ac/game_setup_struct.h"
 #include "ags/shared/ac/game_version.h"
+#include "ags/shared/util/stream.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
 
-
+using namespace AGS::Shared;
 
 // return the type name of the object
 const char *CCCharacter::GetType() {
@@ -37,11 +38,15 @@ const char *CCCharacter::GetType() {
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-int CCCharacter::Serialize(const char *address, char *buffer, int bufsize) {
-	const CharacterInfo *chaa = (const CharacterInfo *)address;
-	StartSerialize(buffer);
-	SerializeInt(chaa->index_id);
-	return EndSerialize();
+size_t CCCharacter::CalcSerializeSize() {
+	return sizeof(int32_t);
+}
+
+// serialize the object into BUFFER (which is BUFSIZE bytes)
+// return number of bytes used
+void CCCharacter::Serialize(const char *address, Stream *out) {
+	CharacterInfo *chaa = (CharacterInfo *)address;
+	out->WriteInt32(chaa->index_id);
 }
 
 void CCCharacter::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/cc_character.h b/engines/ags/engine/ac/dynobj/cc_character.h
index dc51b51d2c7..e344b3befe0 100644
--- a/engines/ags/engine/ac/dynobj/cc_character.h
+++ b/engines/ags/engine/ac/dynobj/cc_character.h
@@ -31,13 +31,14 @@ struct CCCharacter final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	// serialize the object into BUFFER (which is BUFSIZE bytes)
-	// return number of bytes used
-	int Serialize(const char *address, char *buffer, int bufsize) override;
-
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
 
 	void WriteInt16(const char *address, intptr_t offset, int16_t val) override;
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_dialog.cpp b/engines/ags/engine/ac/dynobj/cc_dialog.cpp
index 73c483ed05b..6564812d313 100644
--- a/engines/ags/engine/ac/dynobj/cc_dialog.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_dialog.cpp
@@ -23,22 +23,27 @@
 #include "ags/engine/ac/dialog.h"
 #include "ags/shared/ac/dialog_topic.h"
 #include "ags/shared/ac/game_struct_defines.h"
+#include "ags/shared/util/stream.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 // return the type name of the object
 const char *CCDialog::GetType() {
 	return "Dialog";
 }
 
+size_t CCDialog::CalcSerializeSize() {
+	return sizeof(int32_t);
+}
+
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-int CCDialog::Serialize(const char *address, char *buffer, int bufsize) {
-	const ScriptDialog *shh = (const ScriptDialog *)address;
-	StartSerialize(buffer);
-	SerializeInt(shh->id);
-	return EndSerialize();
+void CCDialog::Serialize(const char *address, Stream *out) {
+	ScriptDialog *shh = (ScriptDialog *)address;
+	out->WriteInt32(shh->id);
 }
 
 void CCDialog::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/cc_dialog.h b/engines/ags/engine/ac/dynobj/cc_dialog.h
index 5d72b90dda2..f42a36fc114 100644
--- a/engines/ags/engine/ac/dynobj/cc_dialog.h
+++ b/engines/ags/engine/ac/dynobj/cc_dialog.h
@@ -31,12 +31,12 @@ struct CCDialog final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	// serialize the object into BUFFER (which is BUFSIZE bytes)
-	// return number of bytes used
-	int Serialize(const char *address, char *buffer, int bufsize) override;
-
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
-
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_gui.cpp b/engines/ags/engine/ac/dynobj/cc_gui.cpp
index c9a31b5e692..104bc6a06cd 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_gui.cpp
@@ -21,22 +21,27 @@
 
 #include "ags/engine/ac/dynobj/cc_gui.h"
 #include "ags/engine/ac/dynobj/script_gui.h"
+#include "ags/shared/util/stream.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 // return the type name of the object
 const char *CCGUI::GetType() {
 	return "GUI";
 }
 
+size_t CCGUI::CalcSerializeSize() {
+	return sizeof(int32_t);
+}
+
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-int CCGUI::Serialize(const char *address, char *buffer, int bufsize) {
-	const ScriptGUI *shh = (const ScriptGUI *)address;
-	StartSerialize(buffer);
-	SerializeInt(shh->id);
-	return EndSerialize();
+void CCGUI::Serialize(const char *address, Stream *out) {
+	ScriptGUI *shh = (ScriptGUI *)address;
+	out->WriteInt32(shh->id);
 }
 
 void CCGUI::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/cc_gui.h b/engines/ags/engine/ac/dynobj/cc_gui.h
index 6ebae4e66da..f9273403b7e 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui.h
+++ b/engines/ags/engine/ac/dynobj/cc_gui.h
@@ -31,11 +31,12 @@ struct CCGUI final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	// serialize the object into BUFFER (which is BUFSIZE bytes)
-	// return number of bytes used
-	int Serialize(const char *address, char *buffer, int bufsize) override;
-
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_gui_object.cpp b/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
index bfb15406e95..8a4c306dff0 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
@@ -23,25 +23,28 @@
 #include "ags/engine/ac/dynobj/script_gui.h"
 #include "ags/shared/gui/gui_main.h"
 #include "ags/shared/gui/gui_object.h"
+#include "ags/shared/util/stream.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
 
-using AGS::Shared::GUIObject;
+using namespace AGS::Shared;
 
 // return the type name of the object
 const char *CCGUIObject::GetType() {
 	return "GUIObject";
 }
 
+size_t CCGUIObject::CalcSerializeSize() {
+	return sizeof(int32_t) * 2;
+}
+
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-int CCGUIObject::Serialize(const char *address, char *buffer, int bufsize) {
-	const GUIObject *guio = (const GUIObject *)address;
-	StartSerialize(buffer);
-	SerializeInt(guio->ParentId);
-	SerializeInt(guio->Id);
-	return EndSerialize();
+void CCGUIObject::Serialize(const char *address, Stream *out) {
+	GUIObject *guio = (GUIObject *)address;
+	out->WriteInt32(guio->ParentId);
+	out->WriteInt32(guio->Id);
 }
 
 void CCGUIObject::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/cc_gui_object.h b/engines/ags/engine/ac/dynobj/cc_gui_object.h
index b655b7d3908..aa2d14996d5 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_gui_object.h
@@ -31,12 +31,12 @@ struct CCGUIObject final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	// serialize the object into BUFFER (which is BUFSIZE bytes)
-	// return number of bytes used
-	int Serialize(const char *address, char *buffer, int bufsize) override;
-
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
-
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_hotspot.cpp b/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
index 2598696aed8..f7eabd70039 100644
--- a/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
@@ -23,22 +23,27 @@
 #include "ags/engine/ac/dynobj/script_hotspot.h"
 #include "ags/shared/ac/common_defines.h"
 #include "ags/shared/game/room_struct.h"
+#include "ags/shared/util/stream.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 // return the type name of the object
 const char *CCHotspot::GetType() {
 	return "Hotspot";
 }
 
+size_t CCHotspot::CalcSerializeSize() {
+	return sizeof(int32_t);
+}
+
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-int CCHotspot::Serialize(const char *address, char *buffer, int bufsize) {
-	const ScriptHotspot *shh = (const ScriptHotspot *)address;
-	StartSerialize(buffer);
-	SerializeInt(shh->id);
-	return EndSerialize();
+void CCHotspot::Serialize(const char *address, Stream *out) {
+	ScriptHotspot *shh = (ScriptHotspot *)address;
+	out->WriteInt32(shh->id);
 }
 
 void CCHotspot::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/cc_hotspot.h b/engines/ags/engine/ac/dynobj/cc_hotspot.h
index a59cadf6bcb..4d076f9b5a6 100644
--- a/engines/ags/engine/ac/dynobj/cc_hotspot.h
+++ b/engines/ags/engine/ac/dynobj/cc_hotspot.h
@@ -31,12 +31,12 @@ struct CCHotspot final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	// serialize the object into BUFFER (which is BUFSIZE bytes)
-	// return number of bytes used
-	int Serialize(const char *address, char *buffer, int bufsize) override;
-
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
-
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_inventory.cpp b/engines/ags/engine/ac/dynobj/cc_inventory.cpp
index 58bd327cd64..5ec104af292 100644
--- a/engines/ags/engine/ac/dynobj/cc_inventory.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_inventory.cpp
@@ -22,22 +22,27 @@
 #include "ags/engine/ac/dynobj/cc_inventory.h"
 #include "ags/engine/ac/dynobj/script_inv_item.h"
 #include "ags/shared/ac/character_info.h"
+#include "ags/shared/util/stream.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 // return the type name of the object
 const char *CCInventory::GetType() {
 	return "Inventory";
 }
 
+size_t CCInventory::CalcSerializeSize() {
+	return sizeof(int32_t);
+}
+
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-int CCInventory::Serialize(const char *address, char *buffer, int bufsize) {
-	const ScriptInvItem *shh = (const ScriptInvItem *)address;
-	StartSerialize(buffer);
-	SerializeInt(shh->id);
-	return EndSerialize();
+void CCInventory::Serialize(const char *address, Stream *out) {
+	ScriptInvItem *shh = (ScriptInvItem *)address;
+	out->WriteInt32(shh->id);
 }
 
 void CCInventory::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/cc_inventory.h b/engines/ags/engine/ac/dynobj/cc_inventory.h
index 6e6e9baec86..49d152a26f3 100644
--- a/engines/ags/engine/ac/dynobj/cc_inventory.h
+++ b/engines/ags/engine/ac/dynobj/cc_inventory.h
@@ -31,12 +31,12 @@ struct CCInventory final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	// serialize the object into BUFFER (which is BUFSIZE bytes)
-	// return number of bytes used
-	int Serialize(const char *address, char *buffer, int bufsize) override;
-
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
-
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_object.cpp b/engines/ags/engine/ac/dynobj/cc_object.cpp
index 68f6a9bc26c..79ede02f9aa 100644
--- a/engines/ags/engine/ac/dynobj/cc_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_object.cpp
@@ -23,22 +23,27 @@
 #include "ags/engine/ac/dynobj/script_object.h"
 #include "ags/shared/ac/common_defines.h"
 #include "ags/shared/game/room_struct.h"
+#include "ags/shared/util/stream.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 // return the type name of the object
 const char *CCObject::GetType() {
 	return "Object";
 }
 
+size_t CCObject::CalcSerializeSize() {
+	return sizeof(int32_t);
+}
+
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-int CCObject::Serialize(const char *address, char *buffer, int bufsize) {
-	const ScriptObject *shh = (const ScriptObject *)address;
-	StartSerialize(buffer);
-	SerializeInt(shh->id);
-	return EndSerialize();
+void CCObject::Serialize(const char *address, Stream *out) {
+	ScriptObject *shh = (ScriptObject *)address;
+	out->WriteInt32(shh->id);
 }
 
 void CCObject::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/cc_object.h b/engines/ags/engine/ac/dynobj/cc_object.h
index 4a8dee85820..0e042976a1d 100644
--- a/engines/ags/engine/ac/dynobj/cc_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_object.h
@@ -31,12 +31,12 @@ struct CCObject final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	// serialize the object into BUFFER (which is BUFSIZE bytes)
-	// return number of bytes used
-	int Serialize(const char *address, char *buffer, int bufsize) override;
-
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
-
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_region.cpp b/engines/ags/engine/ac/dynobj/cc_region.cpp
index 89a2461e865..c540dc8e013 100644
--- a/engines/ags/engine/ac/dynobj/cc_region.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_region.cpp
@@ -23,22 +23,27 @@
 #include "ags/engine/ac/dynobj/script_region.h"
 #include "ags/shared/ac/common_defines.h"
 #include "ags/shared/game/room_struct.h"
+#include "ags/shared/util/stream.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 // return the type name of the object
 const char *CCRegion::GetType() {
 	return "Region";
 }
 
+size_t CCRegion::CalcSerializeSize() {
+	return sizeof(int32_t);
+}
+
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-int CCRegion::Serialize(const char *address, char *buffer, int bufsize) {
-	const ScriptRegion *shh = (const ScriptRegion *)address;
-	StartSerialize(buffer);
-	SerializeInt(shh->id);
-	return EndSerialize();
+void CCRegion::Serialize(const char *address, Stream *out) {
+	ScriptRegion *shh = (ScriptRegion *)address;
+	out->WriteInt32(shh->id);
 }
 
 void CCRegion::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/cc_region.h b/engines/ags/engine/ac/dynobj/cc_region.h
index 4acefbee2fc..3d225ae4deb 100644
--- a/engines/ags/engine/ac/dynobj/cc_region.h
+++ b/engines/ags/engine/ac/dynobj/cc_region.h
@@ -31,12 +31,12 @@ struct CCRegion final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	// serialize the object into BUFFER (which is BUFSIZE bytes)
-	// return number of bytes used
-	int Serialize(const char *address, char *buffer, int bufsize) override;
-
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
-
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_camera.cpp b/engines/ags/engine/ac/dynobj/script_camera.cpp
index 46433d0ba27..36d3ce8d303 100644
--- a/engines/ags/engine/ac/dynobj/script_camera.cpp
+++ b/engines/ags/engine/ac/dynobj/script_camera.cpp
@@ -22,6 +22,7 @@
 #include "ags/engine/ac/dynobj/script_camera.h"
 #include "ags/engine/ac/game_state.h"
 #include "ags/shared/util/bbop.h"
+#include "ags/shared/util/stream.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
@@ -42,10 +43,12 @@ int ScriptCamera::Dispose(const char *address, bool force) {
 	return 1;
 }
 
-int ScriptCamera::Serialize(const char *address, char *buffer, int bufsize) {
-	StartSerialize(buffer);
-	SerializeInt(_id);
-	return EndSerialize();
+size_t ScriptCamera::CalcSerializeSize() {
+	return sizeof(int32_t);
+}
+
+void ScriptCamera::Serialize(const char *address, Stream *out) {
+	out->WriteInt32(_id);
 }
 
 void ScriptCamera::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/script_camera.h b/engines/ags/engine/ac/dynobj/script_camera.h
index d04ad4e7cc3..e984cead4a5 100644
--- a/engines/ags/engine/ac/dynobj/script_camera.h
+++ b/engines/ags/engine/ac/dynobj/script_camera.h
@@ -45,8 +45,12 @@ public:
 
 	const char *GetType() override;
 	int Dispose(const char *address, bool force) override;
-	int Serialize(const char *address, char *buffer, int bufsize) override;
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 
 private:
 	int _id = -1; // index of camera in the game state array
diff --git a/engines/ags/engine/ac/dynobj/script_date_time.cpp b/engines/ags/engine/ac/dynobj/script_date_time.cpp
index 43aa75aabd1..49fc9eb6d37 100644
--- a/engines/ags/engine/ac/dynobj/script_date_time.cpp
+++ b/engines/ags/engine/ac/dynobj/script_date_time.cpp
@@ -20,9 +20,12 @@
  */
 
 #include "ags/engine/ac/dynobj/script_date_time.h"
+#include "ags/shared/util/stream.h"
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 int ScriptDateTime::Dispose(const char *address, bool force) {
 	// always dispose a DateTime
 	delete this;
@@ -33,16 +36,18 @@ const char *ScriptDateTime::GetType() {
 	return "DateTime";
 }
 
-int ScriptDateTime::Serialize(const char *address, char *buffer, int bufsize) {
-	StartSerialize(buffer);
-	SerializeInt(year);
-	SerializeInt(month);
-	SerializeInt(day);
-	SerializeInt(hour);
-	SerializeInt(minute);
-	SerializeInt(second);
-	SerializeInt(rawUnixTime);
-	return EndSerialize();
+size_t ScriptDateTime::CalcSerializeSize() {
+	return sizeof(int32_t) * 7;
+}
+
+void ScriptDateTime::Serialize(const char *address, Stream *out) {
+	out->WriteInt32(year);
+	out->WriteInt32(month);
+	out->WriteInt32(day);
+	out->WriteInt32(hour);
+	out->WriteInt32(minute);
+	out->WriteInt32(second);
+	out->WriteInt32(rawUnixTime);
 }
 
 void ScriptDateTime::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/script_date_time.h b/engines/ags/engine/ac/dynobj/script_date_time.h
index 93cadf571c7..8a6dd92faaa 100644
--- a/engines/ags/engine/ac/dynobj/script_date_time.h
+++ b/engines/ags/engine/ac/dynobj/script_date_time.h
@@ -33,10 +33,15 @@ struct ScriptDateTime final : AGSCCDynamicObject {
 
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	int Serialize(const char *address, char *buffer, int bufsize) override;
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
 
 	ScriptDateTime();
+
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
index cd3dd099ea6..1ec2f61e358 100644
--- a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
@@ -20,18 +20,24 @@
  */
 
 #include "ags/engine/ac/dynobj/script_dialog_options_rendering.h"
+#include "ags/shared/util/stream.h"
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 // return the type name of the object
 const char *ScriptDialogOptionsRendering::GetType() {
 	return "DialogOptionsRendering";
 }
 
+size_t ScriptDialogOptionsRendering::CalcSerializeSize() {
+	return 0;
+}
+
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-int ScriptDialogOptionsRendering::Serialize(const char *address, char *buffer, int bufsize) {
-	return 0;
+void ScriptDialogOptionsRendering::Serialize(const char *address, Stream *out) {
 }
 
 void ScriptDialogOptionsRendering::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
index 92f3cb0fafe..759f33d7f16 100644
--- a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
+++ b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
@@ -41,15 +41,17 @@ struct ScriptDialogOptionsRendering final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	// serialize the object into BUFFER (which is BUFSIZE bytes)
-	// return number of bytes used
-	int Serialize(const char *address, char *buffer, int bufsize) override;
-
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
 
 	void Reset();
 
 	ScriptDialogOptionsRendering();
+
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_dict.cpp b/engines/ags/engine/ac/dynobj/script_dict.cpp
index 05c51716742..6fbae9279e4 100644
--- a/engines/ags/engine/ac/dynobj/script_dict.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dict.cpp
@@ -33,17 +33,10 @@ const char *ScriptDictBase::GetType() {
 	return "StringDictionary";
 }
 
-int ScriptDictBase::Serialize(const char *address, char *buffer, int bufsize) {
-	size_t total_sz = CalcSerializeSize() + sizeof(int32_t) * 2;
-	if (bufsize < 0 || total_sz > (size_t)bufsize) {
-		// buffer not big enough, ask for a bigger one
-		return -((int)total_sz);
-	}
-	StartSerialize(buffer);
-	SerializeInt(IsSorted());
-	SerializeInt(IsCaseSensitive());
-	SerializeContainer();
-	return EndSerialize();
+void ScriptDictBase::Serialize(const char *address, Stream *out) {
+	out->WriteInt32(IsSorted());
+	out->WriteInt32(IsCaseSensitive());
+	SerializeContainer(out);
 }
 
 void ScriptDictBase::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/script_dict.h b/engines/ags/engine/ac/dynobj/script_dict.h
index 214fad6ab14..8a8073962a9 100644
--- a/engines/ags/engine/ac/dynobj/script_dict.h
+++ b/engines/ags/engine/ac/dynobj/script_dict.h
@@ -38,6 +38,7 @@
 #include "ags/lib/std/map.h"
 #include "ags/lib/std/map.h"
 #include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
+#include "ags/shared/util/stream.h"
 #include "ags/shared/util/string.h"
 #include "ags/shared/util/string_types.h"
 
@@ -49,7 +50,6 @@ class ScriptDictBase : public AGSCCDynamicObject {
 public:
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	int Serialize(const char *address, char *buffer, int bufsize) override;
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
 
 	virtual bool IsCaseSensitive() const = 0;
@@ -63,10 +63,14 @@ public:
 	virtual int GetItemCount() = 0;
 	virtual void GetKeys(std::vector<const char *> &buf) const = 0;
 	virtual void GetValues(std::vector<const char *> &buf) const = 0;
+protected:
+	// Calculate and return required space for serialization, in bytes
+	virtual size_t CalcSerializeSize() = 0;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 
 private:
-	virtual size_t CalcSerializeSize() = 0;
-	virtual void SerializeContainer() = 0;
+	virtual void SerializeContainer(AGS::Shared::Stream *out) = 0;
 	virtual void UnserializeContainer(const char *serializedData) = 0;
 };
 
@@ -145,7 +149,9 @@ private:
 	}
 
 	size_t CalcSerializeSize() override {
-		size_t total_sz = sizeof(int32_t);
+		// 2 class properties + item count
+		size_t total_sz = sizeof(int32_t) * 3;
+		// (int32 + string buffer) per item
 		for (auto it = _dic.begin(); it != _dic.end(); ++it) {
 			total_sz += sizeof(int32_t) + it->_key.GetLength();
 			total_sz += sizeof(int32_t) + it->_value.GetLength();
@@ -153,18 +159,17 @@ private:
 		return total_sz;
 	}
 
-	void SerializeContainer() override {
-		SerializeInt((int)_dic.size());
-		for (auto it = _dic.begin(); it != _dic.end(); ++it) {
-			SerializeInt((int)it->_key.GetLength());
-			memcpy(&serbuffer[bytesSoFar], it->_key.GetCStr(), it->_key.GetLength());
-			bytesSoFar += it->_key.GetLength();
-
-			SerializeInt((int)it->_value.GetLength());
-			memcpy(&serbuffer[bytesSoFar], it->_value.GetCStr(), it->_value.GetLength());
-			bytesSoFar += it->_value.GetLength();
-		}
-	}
+	void SerializeContainer(AGS::Shared::Stream *out) override
+    {
+        out->WriteInt32((int)_dic.size());
+        for (auto it = _dic.begin(); it != _dic.end(); ++it)
+        {
+            out->WriteInt32((int)it->_key.GetLength());
+            out->Write(it->_key.GetCStr(), it->_key.GetLength());
+            out->WriteInt32((int)it->_value.GetLength());
+            out->Write(it->_value.GetCStr(), it->_value.GetLength());
+        }
+    }
 
 	void UnserializeContainer(const char *serializedData) override {
 		size_t item_count = (size_t)UnserializeInt();
diff --git a/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp b/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
index e247d590ef9..c48d0cf3fd4 100644
--- a/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
+++ b/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
@@ -74,23 +74,25 @@ const char *ScriptDrawingSurface::GetType() {
 	return "DrawingSurface";
 }
 
-int ScriptDrawingSurface::Serialize(const char *address, char *buffer, int bufsize) {
-	StartSerialize(buffer);
+size_t ScriptDrawingSurface::CalcSerializeSize() {
+	return sizeof(int32_t) * 9;
+}
+
+void ScriptDrawingSurface::Serialize(const char *address, Stream *out) {
 	// pack mask type in the last byte of a negative integer
 	// note: (-1) is reserved for "unused", for backward compatibility
 	if (roomMaskType > 0)
-		SerializeInt(0xFFFFFF00 | roomMaskType);
+		out->WriteInt32(0xFFFFFF00 | roomMaskType);
 	else
-		SerializeInt(roomBackgroundNumber);
-	SerializeInt(dynamicSpriteNumber);
-	SerializeInt(dynamicSurfaceNumber);
-	SerializeInt(currentColour);
-	SerializeInt(currentColourScript);
-	SerializeInt(highResCoordinates);
-	SerializeInt(modified);
-	SerializeInt(hasAlphaChannel);
-	SerializeInt(isLinkedBitmapOnly ? 1 : 0);
-	return EndSerialize();
+		out->WriteInt32(roomBackgroundNumber);
+	out->WriteInt32(dynamicSpriteNumber);
+	out->WriteInt32(dynamicSurfaceNumber);
+	out->WriteInt32(currentColour);
+	out->WriteInt32(currentColourScript);
+	out->WriteInt32(highResCoordinates);
+	out->WriteInt32(modified);
+	out->WriteInt32(hasAlphaChannel);
+	out->WriteInt32(isLinkedBitmapOnly ? 1 : 0);
 }
 
 void ScriptDrawingSurface::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/script_drawing_surface.h b/engines/ags/engine/ac/dynobj/script_drawing_surface.h
index 9bdc57bc751..d169d2f7f64 100644
--- a/engines/ags/engine/ac/dynobj/script_drawing_surface.h
+++ b/engines/ags/engine/ac/dynobj/script_drawing_surface.h
@@ -51,7 +51,6 @@ struct ScriptDrawingSurface final : AGSCCDynamicObject {
 
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	int Serialize(const char *address, char *buffer, int bufsize) override;
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
 	Shared::Bitmap *GetBitmapSurface();
 	Shared::Bitmap *StartDrawing();
@@ -63,6 +62,12 @@ struct ScriptDrawingSurface final : AGSCCDynamicObject {
 	void FinishedDrawingReadOnly();
 
 	ScriptDrawingSurface();
+
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
index f6d7af47309..4feb4b94fa9 100644
--- a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
@@ -20,10 +20,13 @@
  */
 
 #include "ags/engine/ac/dynobj/script_dynamic_sprite.h"
+#include "ags/shared/util/stream.h"
 #include "ags/engine/ac/dynamic_sprite.h"
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 int ScriptDynamicSprite::Dispose(const char *address, bool force) {
 	// always dispose
 	if ((slot) && (!force))
@@ -37,10 +40,12 @@ const char *ScriptDynamicSprite::GetType() {
 	return "DynamicSprite";
 }
 
-int ScriptDynamicSprite::Serialize(const char *address, char *buffer, int bufsize) {
-	StartSerialize(buffer);
-	SerializeInt(slot);
-	return EndSerialize();
+size_t ScriptDynamicSprite::CalcSerializeSize() {
+	return sizeof(int32_t);
+}
+
+void ScriptDynamicSprite::Serialize(const char *address, Stream *out) {
+	out->WriteInt32(slot);
 }
 
 void ScriptDynamicSprite::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
index 1173e80ed73..b2b276aa4d4 100644
--- a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
+++ b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
@@ -31,11 +31,16 @@ struct ScriptDynamicSprite final : AGSCCDynamicObject {
 
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	int Serialize(const char *address, char *buffer, int bufsize) override;
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
 
 	ScriptDynamicSprite(int slot);
 	ScriptDynamicSprite();
+
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_overlay.cpp b/engines/ags/engine/ac/dynobj/script_overlay.cpp
index f28cd5e3aa3..a9073dfc1a7 100644
--- a/engines/ags/engine/ac/dynobj/script_overlay.cpp
+++ b/engines/ags/engine/ac/dynobj/script_overlay.cpp
@@ -21,6 +21,7 @@
 
 #include "ags/engine/ac/dynobj/script_overlay.h"
 #include "ags/shared/ac/common.h"
+#include "ags/shared/util/stream.h"
 #include "ags/engine/ac/overlay.h"
 #include "ags/engine/ac/runtime_defines.h"
 #include "ags/engine/ac/screen_overlay.h"
@@ -29,6 +30,8 @@
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 int ScriptOverlay::Dispose(const char *address, bool force) {
 	// since the managed object is being deleted, remove the
 	// reference so it doesn't try and dispose something else
@@ -53,13 +56,15 @@ const char *ScriptOverlay::GetType() {
 	return "Overlay";
 }
 
-int ScriptOverlay::Serialize(const char *address, char *buffer, int bufsize) {
-	StartSerialize(buffer);
-	SerializeInt(overlayId);
-	SerializeInt(borderWidth);
-	SerializeInt(borderHeight);
-	SerializeInt(isBackgroundSpeech);
-	return EndSerialize();
+size_t ScriptOverlay::CalcSerializeSize() {
+	return sizeof(int32_t) * 4;
+}
+
+void ScriptOverlay::Serialize(const char *address, Stream *out) {
+	out->WriteInt32(overlayId);
+	out->WriteInt32(borderWidth);
+	out->WriteInt32(borderHeight);
+	out->WriteInt32(isBackgroundSpeech);
 }
 
 void ScriptOverlay::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/script_overlay.h b/engines/ags/engine/ac/dynobj/script_overlay.h
index 432b2011700..c354b8dea33 100644
--- a/engines/ags/engine/ac/dynobj/script_overlay.h
+++ b/engines/ags/engine/ac/dynobj/script_overlay.h
@@ -34,10 +34,15 @@ struct ScriptOverlay final : AGSCCDynamicObject {
 
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	int Serialize(const char *address, char *buffer, int bufsize) override;
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
 	void Remove();
 	ScriptOverlay();
+
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_set.cpp b/engines/ags/engine/ac/dynobj/script_set.cpp
index 98dd0bfdb5e..171fa74cc03 100644
--- a/engines/ags/engine/ac/dynobj/script_set.cpp
+++ b/engines/ags/engine/ac/dynobj/script_set.cpp
@@ -20,7 +20,7 @@
  */
 
 #include "ags/engine/ac/dynobj/script_set.h"
-
+#include "ags/shared/util/stream.h"
 namespace AGS3 {
 
 int ScriptSetBase::Dispose(const char *address, bool force) {
@@ -33,17 +33,10 @@ const char *ScriptSetBase::GetType() {
 	return "StringSet";
 }
 
-int ScriptSetBase::Serialize(const char *address, char *buffer, int bufsize) {
-	size_t total_sz = CalcSerializeSize() + sizeof(int32_t) * 2;
-	if (bufsize < 0 || total_sz > (size_t)bufsize) {
-		// buffer not big enough, ask for a bigger one
-		return -((int)total_sz);
-	}
-	StartSerialize(buffer);
-	SerializeInt(IsSorted());
-	SerializeInt(IsCaseSensitive());
-	SerializeContainer();
-	return EndSerialize();
+void ScriptSetBase::Serialize(const char *address, Stream *out) {
+	out->WriteInt32(IsSorted());
+	out->WriteInt32(IsCaseSensitive());
+	SerializeContainer(out);
 }
 
 void ScriptSetBase::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/script_set.h b/engines/ags/engine/ac/dynobj/script_set.h
index a892e9e7159..aad57e2e280 100644
--- a/engines/ags/engine/ac/dynobj/script_set.h
+++ b/engines/ags/engine/ac/dynobj/script_set.h
@@ -37,6 +37,7 @@
 #include "ags/lib/std/set.h"
 #include "ags/lib/std/unordered_set.h"
 #include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
+#include "ags/shared/util/stream.h"
 #include "ags/shared/util/string.h"
 #include "ags/shared/util/string_types.h"
 
@@ -48,7 +49,6 @@ class ScriptSetBase : public AGSCCDynamicObject {
 public:
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	int Serialize(const char *address, char *buffer, int bufsize) override;
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
 
 	virtual bool IsCaseSensitive() const = 0;
@@ -61,9 +61,14 @@ public:
 	virtual int GetItemCount() const = 0;
 	virtual void GetItems(std::vector<const char *> &buf) const = 0;
 
-private:
+protected:
+	// Calculate and return required space for serialization, in bytes
 	virtual size_t CalcSerializeSize() = 0;
-	virtual void SerializeContainer() = 0;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+
+private:
+	virtual void SerializeContainer(AGS::Shared::Stream *out) = 0;
 	virtual void UnserializeContainer(const char *serializedData) = 0;
 };
 
@@ -117,18 +122,19 @@ private:
 	}
 
 	size_t CalcSerializeSize() override {
-		size_t total_sz = sizeof(int32_t);
+		// 2 class properties + item count
+		size_t total_sz = sizeof(int32_t) * 3;
+		// (int32 + string buffer) per item
 		for (auto it = _set.begin(); it != _set.end(); ++it)
 			total_sz += sizeof(int32_t) + it->GetLength();
 		return total_sz;
 	}
 
-	void SerializeContainer() override {
-		SerializeInt((int)_set.size());
+	void SerializeContainer(AGS::Shared::Stream *out) override {
+		out->WriteInt32((int)_set.size());
 		for (auto it = _set.begin(); it != _set.end(); ++it) {
-			SerializeInt((int)it->GetLength());
-			memcpy(&serbuffer[bytesSoFar], it->GetCStr(), it->GetLength());
-			bytesSoFar += it->GetLength();
+			out->WriteInt32((int)it->GetLength());
+			out->Write(it->GetCStr(), it->GetLength());
 		}
 	}
 
diff --git a/engines/ags/engine/ac/dynobj/script_string.cpp b/engines/ags/engine/ac/dynobj/script_string.cpp
index 0cb8af22adf..236791276f1 100644
--- a/engines/ags/engine/ac/dynobj/script_string.cpp
+++ b/engines/ags/engine/ac/dynobj/script_string.cpp
@@ -21,9 +21,13 @@
 
 #include "ags/engine/ac/dynobj/script_string.h"
 #include "ags/engine/ac/string.h"
+#include "ags/engine/ac/string.h"
+#include "ags/shared/util/stream.h"
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 DynObjectRef ScriptString::CreateString(const char *fromText) {
 	return CreateNewScriptStringObj(fromText);
 }
@@ -42,34 +46,33 @@ const char *ScriptString::GetType() {
 	return "String";
 }
 
-int ScriptString::Serialize(const char *address, char *buffer, int bufsize) {
-	StartSerialize(buffer);
-
-	auto toSerialize = text ? text : "";
-
-	auto len = strlen(toSerialize);
-	SerializeInt(len);
-	strcpy(&serbuffer[bytesSoFar], toSerialize);
-	bytesSoFar += len + 1;
+size_t ScriptString::CalcSerializeSize() {
+	return _len + 1 + sizeof(int32_t);
+}
 
-	return EndSerialize();
+void ScriptString::Serialize(const char *address, Stream *out) {
+	const auto *cstr = text ? text : "";
+	out->WriteInt32(_len);
+	out->Write(cstr, _len + 1);
 }
 
 void ScriptString::Unserialize(int index, const char *serializedData, int dataSize) {
 	StartUnserialize(serializedData, dataSize);
-	int textsize = UnserializeInt();
-	text = (char *)malloc(textsize + 1);
+	_len = UnserializeInt();
+	text = (char *)malloc(_len + 1);
 	strcpy(text, &serializedData[bytesSoFar]);
 	ccRegisterUnserializedObject(index, text, this);
 }
 
 ScriptString::ScriptString() {
 	text = nullptr;
+	_len = 0;
 }
 
 ScriptString::ScriptString(const char *fromText) {
-	text = (char *)malloc(strlen(fromText) + 1);
-	strcpy(text, fromText);
+	_len = strlen(fromText);
+	text = (char *)malloc(_len + 1);
+	memcpy(text, fromText, _len + 1);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_string.h b/engines/ags/engine/ac/dynobj/script_string.h
index 2c1b35e29b2..87b810f2b46 100644
--- a/engines/ags/engine/ac/dynobj/script_string.h
+++ b/engines/ags/engine/ac/dynobj/script_string.h
@@ -19,25 +19,35 @@
  *
  */
 
-#ifndef AGS_ENGINE_AC_DYNOBJ_SCRIPTSTRING_H
-#define AGS_ENGINE_AC_DYNOBJ_SCRIPTSTRING_H
+#ifndef AGS_ENGINE_AC_DYNOBJ_SCRIPT_STRING_H
+#define AGS_ENGINE_AC_DYNOBJ_SCRIPT_STRING_H
 
 #include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
 
 namespace AGS3 {
 
 struct ScriptString final : AGSCCDynamicObject, ICCStringClass {
+	// TODO: the preallocated text buffer may be assigned externally;
+	// find out if it's possible to refactor while keeping same functionality
 	char *text;
 
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	int Serialize(const char *address, char *buffer, int bufsize) override;
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
 
 	DynObjectRef CreateString(const char *fromText) override;
 
 	ScriptString();
 	ScriptString(const char *fromText);
+
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+
+private:
+	size_t _len;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_view_frame.cpp b/engines/ags/engine/ac/dynobj/script_view_frame.cpp
index 23fb22f0a9f..a25074bb585 100644
--- a/engines/ags/engine/ac/dynobj/script_view_frame.cpp
+++ b/engines/ags/engine/ac/dynobj/script_view_frame.cpp
@@ -20,9 +20,12 @@
  */
 
 #include "ags/engine/ac/dynobj/script_view_frame.h"
+#include "ags/shared/util/stream.h"
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 int ScriptViewFrame::Dispose(const char *address, bool force) {
 	// always dispose a ViewFrame
 	delete this;
@@ -33,12 +36,14 @@ const char *ScriptViewFrame::GetType() {
 	return "ViewFrame";
 }
 
-int ScriptViewFrame::Serialize(const char *address, char *buffer, int bufsize) {
-	StartSerialize(buffer);
-	SerializeInt(view);
-	SerializeInt(loop);
-	SerializeInt(frame);
-	return EndSerialize();
+size_t ScriptViewFrame::CalcSerializeSize() {
+	return sizeof(int32_t) * 3;
+}
+
+void ScriptViewFrame::Serialize(const char *address, Stream *out) {
+	out->WriteInt32(view);
+	out->WriteInt32(loop);
+	out->WriteInt32(frame);
 }
 
 void ScriptViewFrame::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/script_view_frame.h b/engines/ags/engine/ac/dynobj/script_view_frame.h
index 1a2c376e9bc..3cf1617278d 100644
--- a/engines/ags/engine/ac/dynobj/script_view_frame.h
+++ b/engines/ags/engine/ac/dynobj/script_view_frame.h
@@ -31,11 +31,16 @@ struct ScriptViewFrame final : AGSCCDynamicObject {
 
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	int Serialize(const char *address, char *buffer, int bufsize) override;
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
 
 	ScriptViewFrame(int p_view, int p_loop, int p_frame);
 	ScriptViewFrame();
+
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_viewport.cpp b/engines/ags/engine/ac/dynobj/script_viewport.cpp
index cbe04a7dfc3..c439e0d9c35 100644
--- a/engines/ags/engine/ac/dynobj/script_viewport.cpp
+++ b/engines/ags/engine/ac/dynobj/script_viewport.cpp
@@ -22,6 +22,7 @@
 #include "ags/engine/ac/dynobj/script_viewport.h"
 #include "ags/engine/ac/game_state.h"
 #include "ags/shared/util/bbop.h"
+#include "ags/shared/util/stream.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
@@ -42,10 +43,12 @@ int ScriptViewport::Dispose(const char *address, bool force) {
 	return 1;
 }
 
-int ScriptViewport::Serialize(const char *address, char *buffer, int bufsize) {
-	StartSerialize(buffer);
-	SerializeInt(_id);
-	return EndSerialize();
+size_t ScriptViewport::CalcSerializeSize() {
+	return sizeof(int32_t);
+}
+
+void ScriptViewport::Serialize(const char *address, Stream *out) {
+	out->WriteInt32(_id);
 }
 
 void ScriptViewport::Unserialize(int index, const char *serializedData, int dataSize) {
diff --git a/engines/ags/engine/ac/dynobj/script_viewport.h b/engines/ags/engine/ac/dynobj/script_viewport.h
index ada68e1c2fb..c202e5ce6ce 100644
--- a/engines/ags/engine/ac/dynobj/script_viewport.h
+++ b/engines/ags/engine/ac/dynobj/script_viewport.h
@@ -44,9 +44,14 @@ public:
 
 	const char *GetType() override;
 	int Dispose(const char *address, bool force) override;
-	int Serialize(const char *address, char *buffer, int bufsize) override;
 	void Unserialize(int index, const char *serializedData, int dataSize) override;
 
+protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize() override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+
 private:
 	int _id = -1; // index of viewport in the game state array
 };


Commit: ecb7e424397055a91e5324235979ff6d31bb5425
    https://github.com/scummvm/scummvm/commit/ecb7e424397055a91e5324235979ff6d31bb5425
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-04-23T22:14:54-07:00

Commit Message:
AGS: unserialize dynamic objects using MemoryStream

>From upstream c9007679f21e8347d34312b0dd078bbbd29f0de5

Changed paths:
    engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
    engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
    engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
    engines/ags/engine/ac/dynobj/cc_audio_channel.h
    engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
    engines/ags/engine/ac/dynobj/cc_audio_clip.h
    engines/ags/engine/ac/dynobj/cc_character.cpp
    engines/ags/engine/ac/dynobj/cc_character.h
    engines/ags/engine/ac/dynobj/cc_dialog.cpp
    engines/ags/engine/ac/dynobj/cc_dialog.h
    engines/ags/engine/ac/dynobj/cc_gui.cpp
    engines/ags/engine/ac/dynobj/cc_gui.h
    engines/ags/engine/ac/dynobj/cc_gui_object.cpp
    engines/ags/engine/ac/dynobj/cc_gui_object.h
    engines/ags/engine/ac/dynobj/cc_hotspot.cpp
    engines/ags/engine/ac/dynobj/cc_hotspot.h
    engines/ags/engine/ac/dynobj/cc_inventory.cpp
    engines/ags/engine/ac/dynobj/cc_inventory.h
    engines/ags/engine/ac/dynobj/cc_object.cpp
    engines/ags/engine/ac/dynobj/cc_object.h
    engines/ags/engine/ac/dynobj/cc_region.cpp
    engines/ags/engine/ac/dynobj/cc_region.h
    engines/ags/engine/ac/dynobj/cc_serializer.cpp
    engines/ags/engine/ac/dynobj/script_camera.cpp
    engines/ags/engine/ac/dynobj/script_camera.h
    engines/ags/engine/ac/dynobj/script_containers.h
    engines/ags/engine/ac/dynobj/script_date_time.cpp
    engines/ags/engine/ac/dynobj/script_date_time.h
    engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
    engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
    engines/ags/engine/ac/dynobj/script_dict.cpp
    engines/ags/engine/ac/dynobj/script_dict.h
    engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
    engines/ags/engine/ac/dynobj/script_drawing_surface.h
    engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
    engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
    engines/ags/engine/ac/dynobj/script_overlay.cpp
    engines/ags/engine/ac/dynobj/script_overlay.h
    engines/ags/engine/ac/dynobj/script_set.cpp
    engines/ags/engine/ac/dynobj/script_set.h
    engines/ags/engine/ac/dynobj/script_string.cpp
    engines/ags/engine/ac/dynobj/script_string.h
    engines/ags/engine/ac/dynobj/script_user_object.cpp
    engines/ags/engine/ac/dynobj/script_user_object.h
    engines/ags/engine/ac/dynobj/script_view_frame.cpp
    engines/ags/engine/ac/dynobj/script_view_frame.h
    engines/ags/engine/ac/dynobj/script_viewport.cpp
    engines/ags/engine/ac/dynobj/script_viewport.h
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/game.h
    engines/ags/engine/ac/script_containers.cpp


diff --git a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
index 285ee571331..47dc7e0b6ad 100644
--- a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
@@ -47,30 +47,6 @@ int AGSCCDynamicObject::Serialize(const char *address, char *buffer, int bufsize
 	return static_cast<int32_t>(mems.GetPosition());
 }
 
-void AGSCCDynamicObject::StartUnserialize(const char *sbuffer, int pTotalBytes) {
-	bytesSoFar = 0;
-	totalBytes = pTotalBytes;
-	serbuffer = const_cast<char *>(sbuffer);
-}
-
-int AGSCCDynamicObject::UnserializeInt() {
-	if (bytesSoFar >= totalBytes) // FIXME: don't use quit! return error instead
-		quit("Unserialise: internal error: read past EOF");
-
-	char *chptr = &serbuffer[bytesSoFar];
-	bytesSoFar += 4;
-	return BBOp::Int32FromLE(*((const int *)chptr));
-}
-
-float AGSCCDynamicObject::UnserializeFloat() {
-	if (bytesSoFar >= totalBytes) // FIXME: don't use quit! return error instead
-		quit("Unserialise: internal error: read past EOF");
-
-	char *chptr = &serbuffer[bytesSoFar];
-	bytesSoFar += 4;
-	return BBOp::FloatFromLE(*((const float *)chptr));
-}
-
 const char *AGSCCDynamicObject::GetFieldPtr(const char *address, intptr_t offset) {
 	return address + offset;
 }
diff --git a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
index b6912a16cfc..bd3d4626589 100644
--- a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
@@ -37,7 +37,8 @@ public:
 
 	// TODO: pass savegame format version
 	int Serialize(const char *address, char *buffer, int bufsize) override;
-	virtual void Unserialize(int index, const char *serializedData, int dataSize) = 0;
+	// Try unserializing the object from the given input stream
+	virtual void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) = 0;
 
 	// Legacy support for reading and writing object values by their relative offset
 	const char *GetFieldPtr(const char *address, intptr_t offset) override;
@@ -54,18 +55,10 @@ public:
 
 protected:
 	// Savegame serialization
-	// TODO: reimplement with the proper memory stream?!
-	int bytesSoFar = 0;
-	int totalBytes = 0;
-	char *serbuffer = nullptr;
-
 	// Calculate and return required space for serialization, in bytes
 	virtual size_t CalcSerializeSize() = 0;
 	// Write object data into the provided stream
 	virtual void Serialize(const char *address, AGS::Shared::Stream *out) = 0;
-	void StartUnserialize(const char *sbuffer, int pTotalBytes);
-	int  UnserializeInt();
-	float UnserializeFloat();
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp b/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
index 6d9b2c71454..8101e981456 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
@@ -42,9 +42,8 @@ void CCAudioChannel::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(ach->id);
 }
 
-void CCAudioChannel::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	int id = UnserializeInt();
+void CCAudioChannel::Unserialize(int index, Stream *in, size_t data_sz) {
+	int id = in->ReadInt32();
 	ccRegisterUnserializedObject(index, &_G(scrAudioChannel)[id], this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_channel.h b/engines/ags/engine/ac/dynobj/cc_audio_channel.h
index c8a250c68c9..9e9f0911a1d 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_channel.h
+++ b/engines/ags/engine/ac/dynobj/cc_audio_channel.h
@@ -28,7 +28,7 @@ namespace AGS3 {
 
 struct CCAudioChannel final : AGSCCDynamicObject {
 	const char *GetType() override;
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
 	size_t CalcSerializeSize() override;
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp b/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
index 353589433c5..06ddf83aaa8 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
@@ -41,9 +41,8 @@ void CCAudioClip::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(ach->id);
 }
 
-void CCAudioClip::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	int id = UnserializeInt();
+void CCAudioClip::Unserialize(int index, Stream *in, size_t data_sz) {
+	int id = in->ReadInt32();
 	ccRegisterUnserializedObject(index, &_GP(game).audioClips[id], this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_clip.h b/engines/ags/engine/ac/dynobj/cc_audio_clip.h
index d4126ec7fef..e9a367a11b8 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_clip.h
+++ b/engines/ags/engine/ac/dynobj/cc_audio_clip.h
@@ -28,7 +28,7 @@ namespace AGS3 {
 
 struct CCAudioClip final : AGSCCDynamicObject {
 	const char *GetType() override;
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
 	size_t CalcSerializeSize() override;
diff --git a/engines/ags/engine/ac/dynobj/cc_character.cpp b/engines/ags/engine/ac/dynobj/cc_character.cpp
index 7f9fd3ac216..e568c8c2c6b 100644
--- a/engines/ags/engine/ac/dynobj/cc_character.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_character.cpp
@@ -49,9 +49,8 @@ void CCCharacter::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(chaa->index_id);
 }
 
-void CCCharacter::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	int num = UnserializeInt();
+void CCCharacter::Unserialize(int index, Stream *in, size_t data_sz) {
+	int num = in->ReadInt32();
 	ccRegisterUnserializedObject(index, &_GP(game).chars[num], this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_character.h b/engines/ags/engine/ac/dynobj/cc_character.h
index e344b3befe0..96ee1607b68 100644
--- a/engines/ags/engine/ac/dynobj/cc_character.h
+++ b/engines/ags/engine/ac/dynobj/cc_character.h
@@ -31,7 +31,7 @@ struct CCCharacter final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
 	void WriteInt16(const char *address, intptr_t offset, int16_t val) override;
 protected:
diff --git a/engines/ags/engine/ac/dynobj/cc_dialog.cpp b/engines/ags/engine/ac/dynobj/cc_dialog.cpp
index 6564812d313..83910b81377 100644
--- a/engines/ags/engine/ac/dynobj/cc_dialog.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_dialog.cpp
@@ -46,9 +46,8 @@ void CCDialog::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(shh->id);
 }
 
-void CCDialog::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	int num = UnserializeInt();
+void CCDialog::Unserialize(int index, Stream *in, size_t data_sz) {
+	int num = in->ReadInt32();
 	ccRegisterUnserializedObject(index, &_G(scrDialog)[num], this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_dialog.h b/engines/ags/engine/ac/dynobj/cc_dialog.h
index f42a36fc114..f4556ecc1da 100644
--- a/engines/ags/engine/ac/dynobj/cc_dialog.h
+++ b/engines/ags/engine/ac/dynobj/cc_dialog.h
@@ -31,7 +31,7 @@ struct CCDialog final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
 	size_t CalcSerializeSize() override;
diff --git a/engines/ags/engine/ac/dynobj/cc_gui.cpp b/engines/ags/engine/ac/dynobj/cc_gui.cpp
index 104bc6a06cd..da551a52511 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_gui.cpp
@@ -44,9 +44,8 @@ void CCGUI::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(shh->id);
 }
 
-void CCGUI::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	int num = UnserializeInt();
+void CCGUI::Unserialize(int index, Stream *in, size_t data_sz) {
+	int num = in->ReadInt32();
 	ccRegisterUnserializedObject(index, &_G(scrGui)[num], this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_gui.h b/engines/ags/engine/ac/dynobj/cc_gui.h
index f9273403b7e..fc6c02e7b2c 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui.h
+++ b/engines/ags/engine/ac/dynobj/cc_gui.h
@@ -31,7 +31,7 @@ struct CCGUI final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
 	size_t CalcSerializeSize() override;
diff --git a/engines/ags/engine/ac/dynobj/cc_gui_object.cpp b/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
index 8a4c306dff0..a067355d3df 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
@@ -47,10 +47,9 @@ void CCGUIObject::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(guio->Id);
 }
 
-void CCGUIObject::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	int guinum = UnserializeInt();
-	int objnum = UnserializeInt();
+void CCGUIObject::Unserialize(int index, Stream *in, size_t data_sz) {
+	int guinum = in->ReadInt32();
+	int objnum = in->ReadInt32();
 	ccRegisterUnserializedObject(index, _GP(guis)[guinum].GetControl(objnum), this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_gui_object.h b/engines/ags/engine/ac/dynobj/cc_gui_object.h
index aa2d14996d5..c27ec74193b 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_gui_object.h
@@ -31,7 +31,7 @@ struct CCGUIObject final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
 	size_t CalcSerializeSize() override;
diff --git a/engines/ags/engine/ac/dynobj/cc_hotspot.cpp b/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
index f7eabd70039..50b235b4ae2 100644
--- a/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
@@ -46,9 +46,8 @@ void CCHotspot::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(shh->id);
 }
 
-void CCHotspot::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	int num = UnserializeInt();
+void CCHotspot::Unserialize(int index, Stream *in, size_t data_sz) {
+	int num = in->ReadInt32();
 	ccRegisterUnserializedObject(index, &_G(scrHotspot)[num], this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_hotspot.h b/engines/ags/engine/ac/dynobj/cc_hotspot.h
index 4d076f9b5a6..b6314dac312 100644
--- a/engines/ags/engine/ac/dynobj/cc_hotspot.h
+++ b/engines/ags/engine/ac/dynobj/cc_hotspot.h
@@ -31,7 +31,7 @@ struct CCHotspot final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
 	size_t CalcSerializeSize() override;
diff --git a/engines/ags/engine/ac/dynobj/cc_inventory.cpp b/engines/ags/engine/ac/dynobj/cc_inventory.cpp
index 5ec104af292..49ca3f33e13 100644
--- a/engines/ags/engine/ac/dynobj/cc_inventory.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_inventory.cpp
@@ -45,9 +45,8 @@ void CCInventory::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(shh->id);
 }
 
-void CCInventory::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	int num = UnserializeInt();
+void CCInventory::Unserialize(int index, Stream *in, size_t data_sz) {
+	int num = in->ReadInt32();
 	ccRegisterUnserializedObject(index, &_G(scrInv)[num], this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_inventory.h b/engines/ags/engine/ac/dynobj/cc_inventory.h
index 49d152a26f3..d4cc8a3e939 100644
--- a/engines/ags/engine/ac/dynobj/cc_inventory.h
+++ b/engines/ags/engine/ac/dynobj/cc_inventory.h
@@ -31,7 +31,7 @@ struct CCInventory final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
 	size_t CalcSerializeSize() override;
diff --git a/engines/ags/engine/ac/dynobj/cc_object.cpp b/engines/ags/engine/ac/dynobj/cc_object.cpp
index 79ede02f9aa..755607fd3fd 100644
--- a/engines/ags/engine/ac/dynobj/cc_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_object.cpp
@@ -46,9 +46,8 @@ void CCObject::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(shh->id);
 }
 
-void CCObject::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	int num = UnserializeInt();
+void CCObject::Unserialize(int index, Stream *in, size_t data_sz) {
+	int num = in->ReadInt32();
 	ccRegisterUnserializedObject(index, &_G(scrObj)[num], this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_object.h b/engines/ags/engine/ac/dynobj/cc_object.h
index 0e042976a1d..84d4e3547ba 100644
--- a/engines/ags/engine/ac/dynobj/cc_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_object.h
@@ -31,7 +31,7 @@ struct CCObject final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
 	size_t CalcSerializeSize() override;
diff --git a/engines/ags/engine/ac/dynobj/cc_region.cpp b/engines/ags/engine/ac/dynobj/cc_region.cpp
index c540dc8e013..309ceb4617c 100644
--- a/engines/ags/engine/ac/dynobj/cc_region.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_region.cpp
@@ -46,9 +46,8 @@ void CCRegion::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(shh->id);
 }
 
-void CCRegion::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	int num = UnserializeInt();
+void CCRegion::Unserialize(int index, Stream *in, size_t data_sz) {
+	int num = in->ReadInt32();
 	ccRegisterUnserializedObject(index, &_G(scrRegion)[num], this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_region.h b/engines/ags/engine/ac/dynobj/cc_region.h
index 3d225ae4deb..bdc89b341f6 100644
--- a/engines/ags/engine/ac/dynobj/cc_region.h
+++ b/engines/ags/engine/ac/dynobj/cc_region.h
@@ -31,7 +31,7 @@ struct CCRegion final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
 	size_t CalcSerializeSize() override;
diff --git a/engines/ags/engine/ac/dynobj/cc_serializer.cpp b/engines/ags/engine/ac/dynobj/cc_serializer.cpp
index cfbbbe993c1..8316cd801e1 100644
--- a/engines/ags/engine/ac/dynobj/cc_serializer.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_serializer.cpp
@@ -19,7 +19,7 @@
  *
  */
 
-//include <string.h>
+#include "ags/shared/util/memory_stream.h"
 #include "ags/engine/ac/dynobj/cc_serializer.h"
 #include "ags/engine/ac/dynobj/all_dynamic_classes.h"
 #include "ags/engine/ac/dynobj/all_script_classes.h"
@@ -36,28 +36,40 @@
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 // *** De-serialization of script objects
 
 void AGSDeSerializer::Unserialize(int index, const char *objectType, const char *serializedData, int dataSize) {
+	if (dataSize < 0) {
+		quitprintf("Unserialise: invalid data size (%d) for object type '%s'", dataSize, objectType);
+		return; // TODO: don't quit, return error
+	}
+	// Note that while our builtin classes may accept Stream object,
+	// classes registered by plugin cannot, because streams are not (yet)
+	// part of the plugin API.
+	size_t data_sz = static_cast<size_t>(dataSize);
+	MemoryStream mems(reinterpret_cast<const uint8_t *>(serializedData), dataSize);
+
 	if (strcmp(objectType, "GUIObject") == 0) {
-		_GP(ccDynamicGUIObject).Unserialize(index, serializedData, dataSize);
+		_GP(ccDynamicGUIObject).Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "Character") == 0) {
-		_GP(ccDynamicCharacter).Unserialize(index, serializedData, dataSize);
+		_GP(ccDynamicCharacter).Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "Hotspot") == 0) {
-		_GP(ccDynamicHotspot).Unserialize(index, serializedData, dataSize);
+		_GP(ccDynamicHotspot).Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "Region") == 0) {
-		_GP(ccDynamicRegion).Unserialize(index, serializedData, dataSize);
+		_GP(ccDynamicRegion).Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "Inventory") == 0) {
-		_GP(ccDynamicInv).Unserialize(index, serializedData, dataSize);
+		_GP(ccDynamicInv).Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "Dialog") == 0) {
-		_GP(ccDynamicDialog).Unserialize(index, serializedData, dataSize);
+		_GP(ccDynamicDialog).Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "GUI") == 0) {
-		_GP(ccDynamicGUI).Unserialize(index, serializedData, dataSize);
+		_GP(ccDynamicGUI).Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "Object") == 0) {
-		_GP(ccDynamicObject).Unserialize(index, serializedData, dataSize);
+		_GP(ccDynamicObject).Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "String") == 0) {
 		ScriptString *scf = new ScriptString();
-		scf->Unserialize(index, serializedData, dataSize);
+		scf->Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "File") == 0) {
 		// files cannot be restored properly -- so just recreate
 		// the object; attempting any operations on it will fail
@@ -65,37 +77,37 @@ void AGSDeSerializer::Unserialize(int index, const char *objectType, const char
 		ccRegisterUnserializedObject(index, scf, scf);
 	} else if (strcmp(objectType, "Overlay") == 0) {
 		ScriptOverlay *scf = new ScriptOverlay();
-		scf->Unserialize(index, serializedData, dataSize);
+		scf->Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "DateTime") == 0) {
 		ScriptDateTime *scf = new ScriptDateTime();
-		scf->Unserialize(index, serializedData, dataSize);
+		scf->Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "ViewFrame") == 0) {
 		ScriptViewFrame *scf = new ScriptViewFrame();
-		scf->Unserialize(index, serializedData, dataSize);
+		scf->Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "DynamicSprite") == 0) {
 		ScriptDynamicSprite *scf = new ScriptDynamicSprite();
-		scf->Unserialize(index, serializedData, dataSize);
+		scf->Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "DrawingSurface") == 0) {
 		ScriptDrawingSurface *sds = new ScriptDrawingSurface();
-		sds->Unserialize(index, serializedData, dataSize);
+		sds->Unserialize(index, &mems, data_sz);
 
 		if (sds->isLinkedBitmapOnly) {
 			_G(dialogOptionsRenderingSurface) = sds;
 		}
 	} else if (strcmp(objectType, "DialogOptionsRendering") == 0) {
-		_GP(ccDialogOptionsRendering).Unserialize(index, serializedData, dataSize);
+		_GP(ccDialogOptionsRendering).Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "StringDictionary") == 0) {
-		Dict_Unserialize(index, serializedData, dataSize);
+		Dict_Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "StringSet") == 0) {
-		Set_Unserialize(index, serializedData, dataSize);
+		Set_Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "Viewport2") == 0) {
-		Viewport_Unserialize(index, serializedData, dataSize);
+		Viewport_Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "Camera2") == 0) {
-		Camera_Unserialize(index, serializedData, dataSize);
+		Camera_Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "UserObject") == 0) {
 		ScriptUserObject *suo = new ScriptUserObject();
-		suo->Unserialize(index, serializedData, dataSize);
-	} else if (!unserialize_audio_script_object(index, objectType, serializedData, dataSize)) {
+		suo->Unserialize(index, &mems, data_sz);
+	} else if (!unserialize_audio_script_object(index, objectType, &mems, data_sz)) {
 		// check if the type is read by a plugin
 		for (int ii = 0; ii < _G(numPluginReaders); ii++) {
 			if (strcmp(objectType, _G(pluginReaders)[ii].type) == 0) {
diff --git a/engines/ags/engine/ac/dynobj/script_camera.cpp b/engines/ags/engine/ac/dynobj/script_camera.cpp
index 36d3ce8d303..bda5c551f93 100644
--- a/engines/ags/engine/ac/dynobj/script_camera.cpp
+++ b/engines/ags/engine/ac/dynobj/script_camera.cpp
@@ -51,18 +51,17 @@ void ScriptCamera::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(_id);
 }
 
-void ScriptCamera::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	_id = UnserializeInt();
+void ScriptCamera::Unserialize(int index, Stream *in, size_t data_sz) {
+	_id = in->ReadInt32();
 	ccRegisterUnserializedObject(index, this, this);
 }
 
-ScriptCamera *Camera_Unserialize(int handle, const char *serializedData, int dataSize) {
+ScriptCamera *Camera_Unserialize(int handle, Stream *in, size_t data_sz) {
 	// The way it works now, we must not create a new script object,
 	// but acquire one from the GameState, which keeps the first reference.
 	// This is essential because GameState should be able to invalidate any
 	// script references when Camera gets removed.
-	const int id = BBOp::Int32FromLE(*((const int *)serializedData));
+	const int id = in->ReadInt32();
 	if (id >= 0) {
 		auto scam = _GP(play).RegisterRoomCamera(id, handle);
 		if (scam)
diff --git a/engines/ags/engine/ac/dynobj/script_camera.h b/engines/ags/engine/ac/dynobj/script_camera.h
index e984cead4a5..555e580122f 100644
--- a/engines/ags/engine/ac/dynobj/script_camera.h
+++ b/engines/ags/engine/ac/dynobj/script_camera.h
@@ -19,8 +19,8 @@
  *
  */
 
-#ifndef AGS_ENGINE_AC_DYNOBJ_SCRIPTCAMERA_H
-#define AGS_ENGINE_AC_DYNOBJ_SCRIPTCAMERA_H
+#ifndef AGS_ENGINE_AC_DYNOBJ_SCRIPT_CAMERA_H
+#define AGS_ENGINE_AC_DYNOBJ_SCRIPT_CAMERA_H
 
 #include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
 
@@ -45,7 +45,7 @@ public:
 
 	const char *GetType() override;
 	int Dispose(const char *address, bool force) override;
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
 	size_t CalcSerializeSize() override;
@@ -57,7 +57,7 @@ private:
 };
 
 // Unserialize camera from the memory stream
-ScriptCamera *Camera_Unserialize(int handle, const char *serializedData, int dataSize);
+ScriptCamera *Camera_Unserialize(int handle, AGS::Shared::Stream *in, size_t data_sz);
 
 } // namespace AGS3
 
diff --git a/engines/ags/engine/ac/dynobj/script_containers.h b/engines/ags/engine/ac/dynobj/script_containers.h
index 0ae3f43dd4d..774a655c440 100644
--- a/engines/ags/engine/ac/dynobj/script_containers.h
+++ b/engines/ags/engine/ac/dynobj/script_containers.h
@@ -30,11 +30,11 @@ class ScriptSetBase;
 // Create and register new dictionary
 ScriptDictBase *Dict_Create(bool sorted, bool case_sensitive);
 // Unserialize dictionary from the memory stream
-ScriptDictBase *Dict_Unserialize(int index, const char *serializedData, int dataSize);
+ScriptDictBase *Dict_Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz);
 // Create and register new set
 ScriptSetBase *Set_Create(bool sorted, bool case_sensitive);
 // Unserialize set from the memory stream
-ScriptSetBase *Set_Unserialize(int index, const char *serializedData, int dataSize);
+ScriptSetBase *Set_Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz);
 
 } // namespace AGS3
 
diff --git a/engines/ags/engine/ac/dynobj/script_date_time.cpp b/engines/ags/engine/ac/dynobj/script_date_time.cpp
index 49fc9eb6d37..d461d6b5ad5 100644
--- a/engines/ags/engine/ac/dynobj/script_date_time.cpp
+++ b/engines/ags/engine/ac/dynobj/script_date_time.cpp
@@ -50,15 +50,14 @@ void ScriptDateTime::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(rawUnixTime);
 }
 
-void ScriptDateTime::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	year = UnserializeInt();
-	month = UnserializeInt();
-	day = UnserializeInt();
-	hour = UnserializeInt();
-	minute = UnserializeInt();
-	second = UnserializeInt();
-	rawUnixTime = UnserializeInt();
+void ScriptDateTime::Unserialize(int index, Stream *in, size_t data_sz) {
+	year = in->ReadInt32();
+	month = in->ReadInt32();
+	day = in->ReadInt32();
+	hour = in->ReadInt32();
+	minute = in->ReadInt32();
+	second = in->ReadInt32();
+	rawUnixTime = in->ReadInt32();
 	ccRegisterUnserializedObject(index, this, this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_date_time.h b/engines/ags/engine/ac/dynobj/script_date_time.h
index 8a6dd92faaa..1394348b2bc 100644
--- a/engines/ags/engine/ac/dynobj/script_date_time.h
+++ b/engines/ags/engine/ac/dynobj/script_date_time.h
@@ -33,7 +33,7 @@ struct ScriptDateTime final : AGSCCDynamicObject {
 
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
 	ScriptDateTime();
 
diff --git a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
index 1ec2f61e358..0f40bc322a2 100644
--- a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
@@ -40,7 +40,7 @@ size_t ScriptDialogOptionsRendering::CalcSerializeSize() {
 void ScriptDialogOptionsRendering::Serialize(const char *address, Stream *out) {
 }
 
-void ScriptDialogOptionsRendering::Unserialize(int index, const char *serializedData, int dataSize) {
+void ScriptDialogOptionsRendering::Unserialize(int index, Stream *in, size_t data_sz) {
 	ccRegisterUnserializedObject(index, this, this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
index 759f33d7f16..5f6ca19b4e7 100644
--- a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
+++ b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
@@ -41,7 +41,7 @@ struct ScriptDialogOptionsRendering final : AGSCCDynamicObject {
 	// return the type name of the object
 	const char *GetType() override;
 
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
 	void Reset();
 
diff --git a/engines/ags/engine/ac/dynobj/script_dict.cpp b/engines/ags/engine/ac/dynobj/script_dict.cpp
index 6fbae9279e4..41b00febc2a 100644
--- a/engines/ags/engine/ac/dynobj/script_dict.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dict.cpp
@@ -39,11 +39,10 @@ void ScriptDictBase::Serialize(const char *address, Stream *out) {
 	SerializeContainer(out);
 }
 
-void ScriptDictBase::Unserialize(int index, const char *serializedData, int dataSize) {
+void ScriptDictBase::Unserialize(int index, Stream *in, size_t data_sz) {
 	// NOTE: we expect sorted/case flags are read by external reader;
 	// this is awkward, but I did not find better design solution atm
-	StartUnserialize(serializedData, dataSize);
-	UnserializeContainer(serializedData);
+	UnserializeContainer(in);
 	ccRegisterUnserializedObject(index, this, this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_dict.h b/engines/ags/engine/ac/dynobj/script_dict.h
index 8a8073962a9..c82df3da3fb 100644
--- a/engines/ags/engine/ac/dynobj/script_dict.h
+++ b/engines/ags/engine/ac/dynobj/script_dict.h
@@ -50,7 +50,7 @@ class ScriptDictBase : public AGSCCDynamicObject {
 public:
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
 	virtual bool IsCaseSensitive() const = 0;
 	virtual bool IsSorted() const = 0;
@@ -71,7 +71,7 @@ protected:
 
 private:
 	virtual void SerializeContainer(AGS::Shared::Stream *out) = 0;
-	virtual void UnserializeContainer(const char *serializedData) = 0;
+	virtual void UnserializeContainer(AGS::Shared::Stream *in) = 0;
 };
 
 template <typename TDict, bool is_sorted, bool is_casesensitive>
@@ -121,9 +121,7 @@ public:
 			return true;
 		}
 
-		size_t key_len = strlen(key);
-		size_t value_len = strlen(value);
-		return TryAddItem(key, key_len, value, value_len);
+		return TryAddItem(String(key), String(value));
 	}
 	int GetItemCount() override {
 		return _dic.size();
@@ -138,11 +136,8 @@ public:
 	}
 
 private:
-	bool TryAddItem(const char *key, size_t key_len, const char *value, size_t value_len) {
-		String elem_key(key, key_len);
-		String elem_value;
-		elem_value.SetString(value, value_len);
-		_dic[elem_key] = elem_value;
+	bool TryAddItem(const String &key, const String &value) {
+		_dic[key] = value;
 		return true;
 	}
 	void DeleteItem(ConstIterator it) { /* do nothing */
@@ -171,18 +166,16 @@ private:
         }
     }
 
-	void UnserializeContainer(const char *serializedData) override {
-		size_t item_count = (size_t)UnserializeInt();
+	void UnserializeContainer(AGS::Shared::Stream *in) override {
+		size_t item_count = in->ReadInt32();
 		for (size_t i = 0; i < item_count; ++i) {
-			size_t key_len = UnserializeInt();
-			int key_pos = bytesSoFar;
-			bytesSoFar += key_len;
-			size_t value_len = UnserializeInt();
+			size_t key_len = in->ReadInt32();
+			String key = String::FromStreamCount(in, key_len);
+			size_t value_len = in->ReadInt32();
 			if (value_len != (size_t)-1) // do not restore keys with null value (old format)
 			{
-				int value_pos = bytesSoFar;
-				bytesSoFar += value_len;
-				TryAddItem(&serializedData[key_pos], key_len, &serializedData[value_pos], value_len);
+				String value = String::FromStreamCount(in, value_len);
+				TryAddItem(key, value);
 			}
 		}
 	}
diff --git a/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp b/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
index c48d0cf3fd4..a253f3f2e68 100644
--- a/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
+++ b/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
@@ -95,22 +95,21 @@ void ScriptDrawingSurface::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(isLinkedBitmapOnly ? 1 : 0);
 }
 
-void ScriptDrawingSurface::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	int room_ds = UnserializeInt();
+void ScriptDrawingSurface::Unserialize(int index, Stream *in, size_t data_sz) {
+	int room_ds = in->ReadInt32();
 	if (room_ds >= 0)
 		roomBackgroundNumber = room_ds;
 	// negative value may contain a mask type
 	else if ((room_ds & 0xFF) != 0xFF)
 		roomMaskType = (RoomAreaMask)(room_ds & 0xFF);
-	dynamicSpriteNumber = UnserializeInt();
-	dynamicSurfaceNumber = UnserializeInt();
-	currentColour = UnserializeInt();
-	currentColourScript = UnserializeInt();
-	highResCoordinates = UnserializeInt();
-	modified = UnserializeInt();
-	hasAlphaChannel = UnserializeInt();
-	isLinkedBitmapOnly = (UnserializeInt() != 0);
+	dynamicSpriteNumber = in->ReadInt32();
+	dynamicSurfaceNumber = in->ReadInt32();
+	currentColour = in->ReadInt32();
+	currentColourScript = in->ReadInt32();
+	highResCoordinates = in->ReadInt32();
+	modified = in->ReadInt32();
+	hasAlphaChannel = in->ReadInt32();
+	isLinkedBitmapOnly = (in->ReadInt32() != 0);
 	ccRegisterUnserializedObject(index, this, this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_drawing_surface.h b/engines/ags/engine/ac/dynobj/script_drawing_surface.h
index d169d2f7f64..12b68540432 100644
--- a/engines/ags/engine/ac/dynobj/script_drawing_surface.h
+++ b/engines/ags/engine/ac/dynobj/script_drawing_surface.h
@@ -51,7 +51,7 @@ struct ScriptDrawingSurface final : AGSCCDynamicObject {
 
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 	Shared::Bitmap *GetBitmapSurface();
 	Shared::Bitmap *StartDrawing();
 	void PointToGameResolution(int *xcoord, int *ycoord);
diff --git a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
index 4feb4b94fa9..db30e0d337b 100644
--- a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
@@ -48,9 +48,9 @@ void ScriptDynamicSprite::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(slot);
 }
 
-void ScriptDynamicSprite::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	slot = UnserializeInt();
+
+void ScriptDynamicSprite::Unserialize(int index, Stream *in, size_t data_sz) {
+	slot = in->ReadInt32();
 	ccRegisterUnserializedObject(index, this, this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
index b2b276aa4d4..319075f1b34 100644
--- a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
+++ b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
@@ -31,7 +31,7 @@ struct ScriptDynamicSprite final : AGSCCDynamicObject {
 
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
 	ScriptDynamicSprite(int slot);
 	ScriptDynamicSprite();
diff --git a/engines/ags/engine/ac/dynobj/script_overlay.cpp b/engines/ags/engine/ac/dynobj/script_overlay.cpp
index a9073dfc1a7..524e5e24b49 100644
--- a/engines/ags/engine/ac/dynobj/script_overlay.cpp
+++ b/engines/ags/engine/ac/dynobj/script_overlay.cpp
@@ -67,12 +67,11 @@ void ScriptOverlay::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(isBackgroundSpeech);
 }
 
-void ScriptOverlay::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	overlayId = UnserializeInt();
-	borderWidth = UnserializeInt();
-	borderHeight = UnserializeInt();
-	isBackgroundSpeech = UnserializeInt();
+void ScriptOverlay::Unserialize(int index, Stream *in, size_t data_sz) {
+	overlayId = in->ReadInt32();
+	borderWidth = in->ReadInt32();
+	borderHeight = in->ReadInt32();
+	isBackgroundSpeech = in->ReadInt32();
 	ccRegisterUnserializedObject(index, this, this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_overlay.h b/engines/ags/engine/ac/dynobj/script_overlay.h
index c354b8dea33..00e735e2114 100644
--- a/engines/ags/engine/ac/dynobj/script_overlay.h
+++ b/engines/ags/engine/ac/dynobj/script_overlay.h
@@ -34,7 +34,7 @@ struct ScriptOverlay final : AGSCCDynamicObject {
 
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 	void Remove();
 	ScriptOverlay();
 
diff --git a/engines/ags/engine/ac/dynobj/script_set.cpp b/engines/ags/engine/ac/dynobj/script_set.cpp
index 171fa74cc03..22775977043 100644
--- a/engines/ags/engine/ac/dynobj/script_set.cpp
+++ b/engines/ags/engine/ac/dynobj/script_set.cpp
@@ -39,11 +39,10 @@ void ScriptSetBase::Serialize(const char *address, Stream *out) {
 	SerializeContainer(out);
 }
 
-void ScriptSetBase::Unserialize(int index, const char *serializedData, int dataSize) {
+void ScriptSetBase::Unserialize(int index, Stream *in, size_t data_sz) {
 	// NOTE: we expect sorted/case flags are read by external reader;
 	// this is awkward, but I did not find better design solution atm
-	StartUnserialize(serializedData, dataSize);
-	UnserializeContainer(serializedData);
+	UnserializeContainer(in);
 	ccRegisterUnserializedObject(index, this, this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_set.h b/engines/ags/engine/ac/dynobj/script_set.h
index aad57e2e280..f7aed2a006e 100644
--- a/engines/ags/engine/ac/dynobj/script_set.h
+++ b/engines/ags/engine/ac/dynobj/script_set.h
@@ -49,7 +49,7 @@ class ScriptSetBase : public AGSCCDynamicObject {
 public:
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
 	virtual bool IsCaseSensitive() const = 0;
 	virtual bool IsSorted() const = 0;
@@ -69,7 +69,7 @@ protected:
 
 private:
 	virtual void SerializeContainer(AGS::Shared::Stream *out) = 0;
-	virtual void UnserializeContainer(const char *serializedData) = 0;
+	virtual void UnserializeContainer(AGS::Shared::Stream *in) = 0;
 };
 
 template <typename TSet, bool is_sorted, bool is_casesensitive>
@@ -88,8 +88,7 @@ public:
 
 	bool Add(const char *item) override {
 		if (!item) return false;
-		size_t len = strlen(item);
-		return TryAddItem(item, len);
+		return TryAddItem(String(item));
 	}
 	void Clear() override {
 		for (auto it = _set.begin(); it != _set.end(); ++it)
@@ -115,11 +114,10 @@ public:
 	}
 
 private:
-	bool TryAddItem(const char *item, size_t len) {
-		return _set.insert(String(item, len))._value;
-	}
-	void DeleteItem(ConstIterator it) { /* do nothing */
+	bool TryAddItem(const String &s) {
+		return _set.insert(s)._value;
 	}
+	void DeleteItem(ConstIterator it) { /* do nothing */ }
 
 	size_t CalcSerializeSize() override {
 		// 2 class properties + item count
@@ -138,12 +136,12 @@ private:
 		}
 	}
 
-	void UnserializeContainer(const char *serializedData) override {
-		size_t item_count = (size_t)UnserializeInt();
+	void UnserializeContainer(AGS::Shared::Stream *in) override {
+		size_t item_count = in->ReadInt32();
 		for (size_t i = 0; i < item_count; ++i) {
-			size_t len = UnserializeInt();
-			TryAddItem(&serializedData[bytesSoFar], len);
-			bytesSoFar += len;
+			size_t len = in->ReadInt32();
+			String item = String::FromStreamCount(in, len);
+			TryAddItem(item);
 		}
 	}
 
diff --git a/engines/ags/engine/ac/dynobj/script_string.cpp b/engines/ags/engine/ac/dynobj/script_string.cpp
index 236791276f1..d6c4d4da094 100644
--- a/engines/ags/engine/ac/dynobj/script_string.cpp
+++ b/engines/ags/engine/ac/dynobj/script_string.cpp
@@ -56,11 +56,11 @@ void ScriptString::Serialize(const char *address, Stream *out) {
 	out->Write(cstr, _len + 1);
 }
 
-void ScriptString::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	_len = UnserializeInt();
+void ScriptString::Unserialize(int index, Stream *in, size_t data_sz) {
+	_len = in->ReadInt32();
 	text = (char *)malloc(_len + 1);
-	strcpy(text, &serializedData[bytesSoFar]);
+	in->Read(text, _len + 1);
+	text[_len] = 0; // for safety
 	ccRegisterUnserializedObject(index, text, this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_string.h b/engines/ags/engine/ac/dynobj/script_string.h
index 87b810f2b46..ae3406c4b76 100644
--- a/engines/ags/engine/ac/dynobj/script_string.h
+++ b/engines/ags/engine/ac/dynobj/script_string.h
@@ -33,7 +33,7 @@ struct ScriptString final : AGSCCDynamicObject, ICCStringClass {
 
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
 	DynObjectRef CreateString(const char *fromText) override;
 
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.cpp b/engines/ags/engine/ac/dynobj/script_user_object.cpp
index 6c33d0e1f12..9edbc406730 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.cpp
+++ b/engines/ags/engine/ac/dynobj/script_user_object.cpp
@@ -20,10 +20,13 @@
  */
 
 #include "ags/lib/std/memory.h"
+#include "ags/shared/util/stream.h"
 #include "ags/engine/ac/dynobj/script_user_object.h"
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
 // return the type name of the object
 const char *ScriptUserObject::GetType() {
 	return "UserObject";
@@ -40,12 +43,12 @@ ScriptUserObject::~ScriptUserObject() {
 
 /* static */ ScriptUserObject *ScriptUserObject::CreateManaged(size_t size) {
 	ScriptUserObject *suo = new ScriptUserObject();
-	suo->Create(nullptr, size);
+	suo->Create(nullptr, nullptr, size);
 	ccRegisterManagedObject(suo, suo);
 	return suo;
 }
 
-void ScriptUserObject::Create(const char *data, size_t size) {
+void ScriptUserObject::Create(const char *data, Stream *in, size_t size) {
 	delete[] _data;
 	_data = nullptr;
 
@@ -54,6 +57,8 @@ void ScriptUserObject::Create(const char *data, size_t size) {
 		_data = new char[size];
 		if (data)
 			memcpy(_data, data, _size);
+		else if (in)
+			in->Read(_data, _size);
 		else
 			memset(_data, 0, _size);
 	}
@@ -73,8 +78,8 @@ int ScriptUserObject::Serialize(const char *address, char *buffer, int bufsize)
 	return _size;
 }
 
-void ScriptUserObject::Unserialize(int index, const char *serializedData, int dataSize) {
-	Create(serializedData, dataSize);
+void ScriptUserObject::Unserialize(int index, Stream *in, size_t data_sz) {
+	Create(nullptr, in, data_sz);
 	ccRegisterUnserializedObject(index, this, this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.h b/engines/ags/engine/ac/dynobj/script_user_object.h
index 6b1901c8715..1454bfe358c 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.h
+++ b/engines/ags/engine/ac/dynobj/script_user_object.h
@@ -41,7 +41,7 @@ protected:
 
 public:
 	static ScriptUserObject *CreateManaged(size_t size);
-	void            Create(const char *data, size_t size);
+	void Create(const char *data, AGS::Shared::Stream *in, size_t size);
 
 	// return the type name of the object
 	const char *GetType() override;
@@ -49,7 +49,7 @@ public:
 	// serialize the object into BUFFER (which is BUFSIZE bytes)
 	// return number of bytes used
 	int Serialize(const char *address, char *buffer, int bufsize) override;
-	virtual void Unserialize(int index, const char *serializedData, int dataSize);
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz);
 
 	// Support for reading and writing object values by their relative offset
 	const char *GetFieldPtr(const char *address, intptr_t offset) override;
diff --git a/engines/ags/engine/ac/dynobj/script_view_frame.cpp b/engines/ags/engine/ac/dynobj/script_view_frame.cpp
index a25074bb585..d270ac36a91 100644
--- a/engines/ags/engine/ac/dynobj/script_view_frame.cpp
+++ b/engines/ags/engine/ac/dynobj/script_view_frame.cpp
@@ -46,11 +46,10 @@ void ScriptViewFrame::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(frame);
 }
 
-void ScriptViewFrame::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	view = UnserializeInt();
-	loop = UnserializeInt();
-	frame = UnserializeInt();
+void ScriptViewFrame::Unserialize(int index, Stream *in, size_t data_sz) {
+	view = in->ReadInt32();
+	loop = in->ReadInt32();
+	frame = in->ReadInt32();
 	ccRegisterUnserializedObject(index, this, this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_view_frame.h b/engines/ags/engine/ac/dynobj/script_view_frame.h
index 3cf1617278d..43e68a5aba6 100644
--- a/engines/ags/engine/ac/dynobj/script_view_frame.h
+++ b/engines/ags/engine/ac/dynobj/script_view_frame.h
@@ -31,7 +31,7 @@ struct ScriptViewFrame final : AGSCCDynamicObject {
 
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
 	ScriptViewFrame(int p_view, int p_loop, int p_frame);
 	ScriptViewFrame();
diff --git a/engines/ags/engine/ac/dynobj/script_viewport.cpp b/engines/ags/engine/ac/dynobj/script_viewport.cpp
index c439e0d9c35..39eff63ce19 100644
--- a/engines/ags/engine/ac/dynobj/script_viewport.cpp
+++ b/engines/ags/engine/ac/dynobj/script_viewport.cpp
@@ -51,18 +51,17 @@ void ScriptViewport::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(_id);
 }
 
-void ScriptViewport::Unserialize(int index, const char *serializedData, int dataSize) {
-	StartUnserialize(serializedData, dataSize);
-	_id = UnserializeInt();
+void ScriptViewport::Unserialize(int index, Stream *in, size_t data_sz) {
+	_id = in->ReadInt32();
 	ccRegisterUnserializedObject(index, this, this);
 }
 
-ScriptViewport *Viewport_Unserialize(int handle, const char *serializedData, int dataSize) {
+ScriptViewport *Viewport_Unserialize(int handle, Stream *in, size_t data_sz) {
 	// The way it works now, we must not create a new script object,
 	// but acquire one from the GameState, which keeps the first reference.
 	// This is essential because GameState should be able to invalidate any
 	// script references when Viewport gets removed.
-	const int id = BBOp::Int32FromLE(*((const int *)serializedData));
+	const int id = in->ReadInt32();
 	if (id >= 0) {
 		auto scview = _GP(play).RegisterRoomViewport(id, handle);
 		if (scview)
diff --git a/engines/ags/engine/ac/dynobj/script_viewport.h b/engines/ags/engine/ac/dynobj/script_viewport.h
index c202e5ce6ce..9d930a87348 100644
--- a/engines/ags/engine/ac/dynobj/script_viewport.h
+++ b/engines/ags/engine/ac/dynobj/script_viewport.h
@@ -44,7 +44,7 @@ public:
 
 	const char *GetType() override;
 	int Dispose(const char *address, bool force) override;
-	void Unserialize(int index, const char *serializedData, int dataSize) override;
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
 protected:
 	// Calculate and return required space for serialization, in bytes
@@ -57,7 +57,7 @@ private:
 };
 
 // Unserialize viewport from the memory stream
-ScriptViewport *Viewport_Unserialize(int handle, const char *serializedData, int dataSize);
+ScriptViewport *Viewport_Unserialize(int handle, AGS::Shared::Stream *in, size_t data_sz);
 
 } // namespace AGS3
 
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 7caf2911b3c..8d3bbf20136 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -1344,11 +1344,11 @@ void get_message_text(int msnum, char *buffer, char giveErr) {
 	replace_tokens(get_translation(_GP(thisroom).Messages[msnum].GetCStr()), buffer, maxlen);
 }
 
-bool unserialize_audio_script_object(int index, const char *objectType, const char *serializedData, int dataSize) {
+bool unserialize_audio_script_object(int index, const char *objectType, Stream *in, size_t data_sz) {
 	if (strcmp(objectType, "AudioChannel") == 0) {
-		_GP(ccDynamicAudio).Unserialize(index, serializedData, dataSize);
+		_GP(ccDynamicAudio).Unserialize(index, in, data_sz);
 	} else if (strcmp(objectType, "AudioClip") == 0) {
-		_GP(ccDynamicAudioClip).Unserialize(index, serializedData, dataSize);
+		_GP(ccDynamicAudioClip).Unserialize(index, in, data_sz);
 	} else {
 		return false;
 	}
diff --git a/engines/ags/engine/ac/game.h b/engines/ags/engine/ac/game.h
index 6f50abc49b0..215a851701c 100644
--- a/engines/ags/engine/ac/game.h
+++ b/engines/ags/engine/ac/game.h
@@ -193,7 +193,7 @@ void replace_tokens(const char *srcmes, char *destm, int maxlen = 99999);
 const char *get_global_message(int msnum);
 void get_message_text(int msnum, char *buffer, char giveErr = 1);
 
-bool unserialize_audio_script_object(int index, const char *objectType, const char *serializedData, int dataSize);
+bool unserialize_audio_script_object(int index, const char *objectType, AGS::Shared::Stream *in, size_t data_sz);
 
 // Notifies the game objects that certain sprite was updated.
 // This make them update their render states, caches, and so on.
diff --git a/engines/ags/engine/ac/script_containers.cpp b/engines/ags/engine/ac/script_containers.cpp
index 758fb958c36..e5c9a22412f 100644
--- a/engines/ags/engine/ac/script_containers.cpp
+++ b/engines/ags/engine/ac/script_containers.cpp
@@ -68,16 +68,13 @@ ScriptDictBase *Dict_Create(bool sorted, bool case_sensitive) {
 }
 
 // TODO: we need memory streams
-ScriptDictBase *Dict_Unserialize(int index, const char *serializedData, int dataSize) {
-	if (dataSize < (int)sizeof(int32_t) * 2)
-		quit("Dict_Unserialize: not enough data.");
-	const char *ptr = serializedData;
-	const int sorted = BBOp::Int32FromLE(*((const int *)ptr));
-	ptr += sizeof(int32_t);
-	const int cs = BBOp::Int32FromLE(*((const int *)ptr));
-	ptr += sizeof(int32_t);
+ScriptDictBase *Dict_Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) {
+	if (data_sz < sizeof(int32_t) * 2)
+		quit("Dict_Unserialize: not enough data."); // TODO: don't quit, return error
+	const int sorted = in->ReadInt32();
+	const int cs = in->ReadInt32();
 	ScriptDictBase *dic = Dict_CreateImpl(sorted != 0, cs != 0);
-	dic->Unserialize(index, ptr, dataSize -= sizeof(int32_t) * 2);
+	dic->Unserialize(index, in, data_sz - sizeof(int32_t) * 2);
 	return dic;
 }
 
@@ -205,16 +202,13 @@ ScriptSetBase *Set_Create(bool sorted, bool case_sensitive) {
 }
 
 // TODO: we need memory streams
-ScriptSetBase *Set_Unserialize(int index, const char *serializedData, int dataSize) {
-	if (dataSize < (int)sizeof(int32_t) * 2)
-		quit("Set_Unserialize: not enough data.");
-	const char *ptr = serializedData;
-	const int sorted = BBOp::Int32FromLE(*((const int *)ptr));
-	ptr += sizeof(int32_t);
-	const int cs = BBOp::Int32FromLE(*((const int *)ptr));
-	ptr += sizeof(int32_t);
+ScriptSetBase *Set_Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) {
+	if (data_sz < sizeof(int32_t) * 2)
+		quit("Set_Unserialize: not enough data."); // TODO: don't quit, return error
+	const int sorted = in->ReadInt32();
+	const int cs = in->ReadInt32();
 	ScriptSetBase *set = Set_CreateImpl(sorted != 0, cs != 0);
-	set->Unserialize(index, ptr, dataSize -= sizeof(int32_t) * 2);
+	set->Unserialize(index, in, data_sz - sizeof(int32_t) * 2);
 	return set;
 }
 


Commit: d3f9f299ddd4c5f21d41d7101eee01f98dc76c03
    https://github.com/scummvm/scummvm/commit/d3f9f299ddd4c5f21d41d7101eee01f98dc76c03
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-04-23T22:19:18-07:00

Commit Message:
* Engine: moved unsupported scStartRecording, get rid of a h/cpp pair

>From upstream 8e8e498776fec5392cbc6d6b4d36c83cce7cf4d0

Changed paths:
  R engines/ags/engine/ac/global_record.cpp
  R engines/ags/engine/ac/global_record.h
    engines/ags/engine/ac/global_api.cpp
    engines/ags/engine/ac/global_game.cpp
    engines/ags/engine/ac/global_game.h
    engines/ags/module.mk
    engines/ags/plugins/core/global_api.cpp


diff --git a/engines/ags/engine/ac/global_api.cpp b/engines/ags/engine/ac/global_api.cpp
index a3e45e25192..0a9faace09a 100644
--- a/engines/ags/engine/ac/global_api.cpp
+++ b/engines/ags/engine/ac/global_api.cpp
@@ -56,7 +56,6 @@
 #include "ags/engine/ac/global_overlay.h"
 #include "ags/engine/ac/global_palette.h"
 #include "ags/engine/ac/global_parser.h"
-#include "ags/engine/ac/global_record.h"
 #include "ags/engine/ac/global_region.h"
 #include "ags/engine/ac/global_room.h"
 #include "ags/engine/ac/global_slider.h"
diff --git a/engines/ags/engine/ac/global_game.cpp b/engines/ags/engine/ac/global_game.cpp
index 2dd9be420bc..87cd69de640 100644
--- a/engines/ags/engine/ac/global_game.cpp
+++ b/engines/ags/engine/ac/global_game.cpp
@@ -811,4 +811,8 @@ void SkipWait() {
 	_GP(play).wait_counter = 0;
 }
 
+void scStartRecording(int /*keyToStop*/) {
+	debug_script_warn("StartRecording: not supported");
+}
+
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/global_game.h b/engines/ags/engine/ac/global_game.h
index f43a6961848..213d516a9af 100644
--- a/engines/ags/engine/ac/global_game.h
+++ b/engines/ags/engine/ac/global_game.h
@@ -109,6 +109,8 @@ int WaitMouse(int nloops);
 int WaitMouseKey(int nloops);
 void SkipWait();
 
+void scStartRecording(int keyToStop);
+
 } // namespace AGS3
 
 #endif
diff --git a/engines/ags/engine/ac/global_record.cpp b/engines/ags/engine/ac/global_record.cpp
deleted file mode 100644
index 5ff44a5ab74..00000000000
--- a/engines/ags/engine/ac/global_record.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include "ags/engine/ac/global_record.h"
-#include "ags/shared/ac/common.h"
-
-namespace AGS3 {
-
-void scStartRecording(int keyToStop) {
-	quit("!StartRecording: not supported");
-}
-
-} // namespace AGS3
diff --git a/engines/ags/engine/ac/global_record.h b/engines/ags/engine/ac/global_record.h
deleted file mode 100644
index ccbb18d9686..00000000000
--- a/engines/ags/engine/ac/global_record.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#ifndef AGS_ENGINE_AC_GLOBAL_RECORD_H
-#define AGS_ENGINE_AC_GLOBAL_RECORD_H
-
-namespace AGS3 {
-
-void scStartRecording(int keyToStop);
-
-} // namespace AGS3
-
-#endif
diff --git a/engines/ags/module.mk b/engines/ags/module.mk
index 257df04c895..62404edf8d9 100644
--- a/engines/ags/module.mk
+++ b/engines/ags/module.mk
@@ -139,7 +139,6 @@ MODULE_OBJS = \
 	engine/ac/global_overlay.o \
 	engine/ac/global_palette.o \
 	engine/ac/global_parser.o \
-	engine/ac/global_record.o \
 	engine/ac/global_region.o \
 	engine/ac/global_room.o \
 	engine/ac/global_screen.o \
diff --git a/engines/ags/plugins/core/global_api.cpp b/engines/ags/plugins/core/global_api.cpp
index b4c84f5fa5b..cc090842c8f 100644
--- a/engines/ags/plugins/core/global_api.cpp
+++ b/engines/ags/plugins/core/global_api.cpp
@@ -47,7 +47,6 @@
 #include "ags/engine/ac/global_overlay.h"
 #include "ags/engine/ac/global_palette.h"
 #include "ags/engine/ac/global_parser.h"
-#include "ags/engine/ac/global_record.h"
 #include "ags/engine/ac/global_region.h"
 #include "ags/engine/ac/global_room.h"
 #include "ags/engine/ac/global_screen.h"


Commit: 38f0fdc47bee954f4c39ae66ea6b959a3c8437cb
    https://github.com/scummvm/scummvm/commit/38f0fdc47bee954f4c39ae66ea6b959a3c8437cb
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-04-23T22:24:24-07:00

Commit Message:
AGS: Renamed locals hiding global mouse vars in InventoryScreen

>From upstream b93bac746a61c88d9de61e62c012a9ecf27cadc4

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


diff --git a/engines/ags/engine/ac/inv_window.cpp b/engines/ags/engine/ac/inv_window.cpp
index d88dade1c96..5ef33f83540 100644
--- a/engines/ags/engine/ac/inv_window.cpp
+++ b/engines/ags/engine/ac/inv_window.cpp
@@ -355,11 +355,11 @@ bool InventoryScreen::Run() {
 	refresh_gui_screen();
 
 	// NOTE: this is because old code was working with full game screen
-	const int mousex = _G(mousex) - windowxp;
-	const int mousey = _G(mousey) - windowyp;
+	const int mx = _G(mousex) - windowxp;
+	const int my = _G(mousey) - windowyp;
 
-	int isonitem = ((mousey - bartop) / highest) * ICONSPERLINE + (mousex - barxp) / widest;
-	if (mousey <= bartop) isonitem = -1;
+	int isonitem = ((my - bartop) / highest) * ICONSPERLINE + (mx - barxp) / widest;
+	if (my <= bartop) isonitem = -1;
 	else if (isonitem >= 0) isonitem += top_item;
 	if ((isonitem < 0) | (isonitem >= numitems) | (isonitem >= top_item + num_visible_items))
 		isonitem = -1;
@@ -370,9 +370,9 @@ bool InventoryScreen::Run() {
 	}
 
 	if (mclick == MouseLeft) {
-		if ((mousey < 0) | (mousey > windowhit) | (mousex < 0) | (mousex > windowwid))
+		if ((my < 0) | (my > windowhit) | (mx < 0) | (mx > windowwid))
 			return true; // continue inventory screen loop
-		if (mousey < buttonyp) {
+		if (my < buttonyp) {
 			int clickedon = isonitem;
 			if (clickedon < 0) return true; // continue inventory screen loop
 			_G(evblocknum) = dii[clickedon].num;
@@ -423,8 +423,8 @@ bool InventoryScreen::Run() {
 			//        break;
 			return true; // continue inventory screen loop
 		} else {
-			if (mousex >= windowwid - ARROWBUTTONWID) {
-				if (mousey < buttonyp + get_fixed_pixel_size(2) + ARROWBUTTONWID) {
+			if (mx >= windowwid - ARROWBUTTONWID) {
+				if (my < buttonyp + get_fixed_pixel_size(2) + ARROWBUTTONWID) {
 					if (top_item > 0) {
 						top_item -= ICONSPERLINE;
 						//ags_domouse(DOMOUSE_DISABLE);
@@ -432,7 +432,7 @@ bool InventoryScreen::Run() {
 						break_code = Redraw();
 						return break_code == 0;
 					}
-				} else if ((mousey < buttonyp + get_fixed_pixel_size(4) + ARROWBUTTONWID * 2) && (top_item + num_visible_items < numitems)) {
+				} else if ((my < buttonyp + get_fixed_pixel_size(4) + ARROWBUTTONWID * 2) && (top_item + num_visible_items < numitems)) {
 					top_item += ICONSPERLINE;
 					//ags_domouse(DOMOUSE_DISABLE);
 
@@ -442,16 +442,14 @@ bool InventoryScreen::Run() {
 				return true; // continue inventory screen loop
 			}
 
-			int buton = mousex - 2;
+			int buton = mx - 2;
 			if (buton < 0) return true; // continue inventory screen loop
 			buton /= BUTTONWID;
 			if (buton >= 3) return true; // continue inventory screen loop
 			if (buton == 0) {
-				toret = -1;
-				cmode = MODE_LOOK;
+				toret = -1; cmode = MODE_LOOK;
 			} else if (buton == 1) {
-				cmode = CURS_ARROW;
-				toret = -1;
+				cmode = CURS_ARROW; toret = -1;
 			} else {
 				return false; // end inventory screen loop
 			}




More information about the Scummvm-git-logs mailing list