From 3a1cec193c51aef2b94e175990c16d14a681c8bb Mon Sep 17 00:00:00 2001 From: Ivan Mogilko Date: Tue, 11 Jul 2023 01:14:42 +0300 Subject: [PATCH] Editor: unescape text properties when writing game data This ensures that special sequences like "\n" are converted to characters '\n', and similar, in properties containing human-readable texts. Normally this is only useful for GUI controls, room messages, and perhaps Custom Properties, but I did it for all of them, for a formal consistency. --- Common/util/string_utils.h | 2 +- Editor/AGS.Editor/DataFileWriter.cs | 34 +++++++++++++++++++---------- Editor/AGS.Native/NativeMethods.cpp | 9 ++++++++ Editor/AGS.Native/NativeUtils.h | 10 +++++++-- Editor/AGS.Native/agsnative.cpp | 12 +++++----- 5 files changed, 47 insertions(+), 20 deletions(-) diff --git a/Common/util/string_utils.h b/Common/util/string_utils.h index 4338d3b9acf..1d0f569ef73 100644 --- a/Common/util/string_utils.h +++ b/Common/util/string_utils.h @@ -50,7 +50,7 @@ namespace StrUtil // returns def_val on failure float StringToFloat(const String &s, float def_val = 0.f); - // A simple unescape string implementation, unescapes '\\x' into '\x'. + // A simple unescape string implementation, unescapes "\\x" into '\x'. String Unescape(const String &s); // Converts a classic wildcard search pattern into C++11 compatible regex pattern String WildcardToRegex(const String &wildcard); diff --git a/Editor/AGS.Editor/DataFileWriter.cs b/Editor/AGS.Editor/DataFileWriter.cs index 64a7c7799ba..abe7f07b8eb 100644 --- a/Editor/AGS.Editor/DataFileWriter.cs +++ b/Editor/AGS.Editor/DataFileWriter.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; namespace AGS.Editor { @@ -283,6 +284,17 @@ static void FilePutNullTerminatedString(string text, BinaryWriter writer) writer.Write(GetTextBytesTerminated(text)); } + /// + /// Applies necessary transformations for the properties with + /// human-readable text, such as: + /// - unescapes special sequences ("\\n" to '\n'), etc. + /// + /// + public static string TextProperty(string text) + { + return Regex.Unescape(text); + } + /// /// Write asset library header with the table of contents. /// Currently corresponds to writing main lib file in chain in format version 30. @@ -528,7 +540,7 @@ public static string MakeFlatDataFile(string[] assetFileNames, int splitSize, st private static void WriteGameSetupStructBase_Aligned(BinaryWriter writer, Game game) { // assume stream is aligned at start - WriteString(SafeTruncate(game.Settings.GameName, 49), 50, writer); + WriteString(SafeTruncate(TextProperty(game.Settings.GameName), 49), 50, writer); writer.Write(new byte[2]); // alignment padding int[] options = new int[100]; options[NativeConstants.GameOptions.OPT_ALWAYSSPCH] = (game.Settings.AlwaysDisplayTextAsSpeech ? 1 : 0); @@ -688,7 +700,7 @@ public static void Write(BinaryWriter writer, CustomProperties properties) foreach (KeyValuePair pair in properties.PropertyValues) { FilePutString(pair.Value.Name, writer); - FilePutString(pair.Value.Value, writer); + FilePutString(TextProperty(pair.Value.Value), writer); } } } @@ -767,7 +779,7 @@ public void Serialize(BinaryWriter writer) for (int i = 0; i < PropertyCount; ++i) { FilePutString(Names[i], writer); - FilePutString(Values[i], writer); + FilePutString(TextProperty(Values[i]), writer); } } @@ -1139,7 +1151,7 @@ private void WriteAllButtonsAndTextWindowEdges() writer.Write(0); // rightclick writer.Write(ctrl.NewModeNumber); // lclickdata writer.Write(0); // rclickdata - FilePutString(ctrl.Text, writer); // text + FilePutString(TextProperty(ctrl.Text), writer); // text writer.Write((int)ctrl.TextAlignment); // textAlignment } } @@ -1151,7 +1163,7 @@ private void WriteAllLabels() { WriteGUIControl(label, 0); string text = label.Text; - FilePutString(text, writer); + FilePutString(TextProperty(text), writer); writer.Write(label.Font); writer.Write(label.TextColor); writer.Write((int)label.TextAlignment); @@ -1191,7 +1203,7 @@ private void WriteAllTextBoxes() foreach (GUITextBox textBox in GUITextBoxes) { WriteGUIControl(textBox, 0, new string[] { textBox.OnActivate }); - FilePutString(textBox.Text, writer); + FilePutString(TextProperty(textBox.Text), writer); writer.Write(textBox.Font); writer.Write(textBox.TextColor); writer.Write(MakeTextBoxFlags(textBox)); @@ -1459,7 +1471,7 @@ public static bool SaveThisGameToFile(string fileName, Game game, CompileMessage writer.Write(new byte[68]); // inventory item slot 0 is unused for (int i = 0; i < game.InventoryItems.Count; ++i) { - WriteString(game.InventoryItems[i].Description, 24, writer); + WriteString(TextProperty(game.InventoryItems[i].Description), 24, writer); writer.Write(new byte[4]); // null terminator plus 3 bytes padding writer.Write(game.InventoryItems[i].Image); writer.Write(game.InventoryItems[i].CursorImage); @@ -1606,7 +1618,7 @@ public static bool SaveThisGameToFile(string fileName, Game game, CompileMessage } writer.Write((short)0); // actx writer.Write((short)0); // acty - WriteString(character.RealName, 40, writer); // name + WriteString(TextProperty(character.RealName), 40, writer); // name WriteString(character.ScriptName, NativeConstants.MAX_SCRIPT_NAME_LEN, writer); // scrname writer.Write((char)1); // on writer.Write((byte)0); // alignment padding @@ -1666,8 +1678,8 @@ public static bool SaveThisGameToFile(string fileName, Game game, CompileMessage { FilePutString(schemaItem.Name, writer); writer.Write((int)schemaItem.Type); - FilePutString(schemaItem.Description, writer); - FilePutString(schemaItem.DefaultValue, writer); + FilePutString(TextProperty(schemaItem.Description), writer); + FilePutString(TextProperty(schemaItem.DefaultValue), writer); } for (int i = 0; i < game.Characters.Count; ++i) { @@ -1739,7 +1751,7 @@ public static bool SaveThisGameToFile(string fileName, Game game, CompileMessage writer.Write(room.Number); if (room.Description != null) { - FilePutNullTerminatedString(room.Description, 500, writer); + FilePutNullTerminatedString(TextProperty(room.Description), 500, writer); } else writer.Write((byte)0); } diff --git a/Editor/AGS.Native/NativeMethods.cpp b/Editor/AGS.Native/NativeMethods.cpp index 328b0ab4697..45d95b30fce 100644 --- a/Editor/AGS.Native/NativeMethods.cpp +++ b/Editor/AGS.Native/NativeMethods.cpp @@ -23,6 +23,7 @@ see the license.txt for details. #include "game/plugininfo.h" #include "util/error.h" #include "util/multifilelib.h" +#include "util/string_utils.h" using namespace System::Runtime::InteropServices; using namespace AGS::Native; @@ -112,6 +113,14 @@ AGSString TextConverter::Convert(System::String^ clr_str) return TextHelper::Convert(clr_str, _encoding); } +AGSString TextConverter::ConvertTextProperty(System::String^ clr_str) +{ + if (clr_str == nullptr) + return AGSString(); + AGSString str = TextHelper::Convert(clr_str, _encoding); + return AGS::Common::StrUtil::Unescape(str); +} + std::string TextConverter::ConvertToStd(System::String^ clr_str) { if (clr_str == nullptr) diff --git a/Editor/AGS.Native/NativeUtils.h b/Editor/AGS.Native/NativeUtils.h index b867c08eb62..bbbe96a5cc7 100644 --- a/Editor/AGS.Native/NativeUtils.h +++ b/Editor/AGS.Native/NativeUtils.h @@ -30,9 +30,15 @@ public ref class TextConverter System::Text::Encoding^ GetEncoding(); // Convert native string to managed using current encoding System::String^ Convert(const AGS::Common::String &str); - // Convert managed string to native using current encoding + // Convert managed string to native using current encoding; + // this is meant for strings containing human-readable texts AGSString Convert(System::String^ clr_str); - // Convert managed string to native std::string using current encoding + // Converts a textual property from managed string to native using current encoding; + // Does additional transformation for human-readable text: + // - unescapes special sequences ("\\n" to '\n') + AGSString ConvertTextProperty(System::String^ clr_str); + // Convert managed string to native std::string using current encoding; + // this is meant for strings containing human-readable texts std::string ConvertToStd(System::String^ clr_str); private: diff --git a/Editor/AGS.Native/agsnative.cpp b/Editor/AGS.Native/agsnative.cpp index 7c7c84b8085..d5ece1ab286 100644 --- a/Editor/AGS.Native/agsnative.cpp +++ b/Editor/AGS.Native/agsnative.cpp @@ -2131,7 +2131,7 @@ void ConvertGUIToBinaryFormat(GUI ^guiObj, GUIMain *gui) nbut.ClickAction[Common::kGUIClickLeft] = (Common::GUIClickAction)button->ClickAction; nbut.ClickData[Common::kGUIClickLeft] = button->NewModeNumber; nbut.SetClipImage(button->ClipImage); - nbut.SetText(tcv->Convert(button->Text)); + nbut.SetText(tcv->ConvertTextProperty(button->Text)); nbut.EventHandlers[0] = TextHelper::ConvertASCII(button->OnClick); guibuts.push_back(nbut); @@ -2143,7 +2143,7 @@ void ConvertGUIToBinaryFormat(GUI ^guiObj, GUIMain *gui) nlabel.TextColor = label->TextColor; nlabel.Font = label->Font; nlabel.TextAlignment = (::HorAlignment)label->TextAlignment; - Common::String text = tcv->Convert(label->Text); + Common::String text = tcv->ConvertTextProperty(label->Text); nlabel.SetText(text); guilabels.push_back(nlabel); @@ -2283,7 +2283,7 @@ void CompileCustomProperties(AGS::Types::CustomProperties ^convertFrom, AGS::Com { AGS::Common::String name, value; name = TextHelper::ConvertASCII(convertFrom->PropertyValues[key]->Name); // property name is ASCII - value = tcv->Convert(convertFrom->PropertyValues[key]->Value); + value = tcv->ConvertTextProperty(convertFrom->PropertyValues[key]->Value); (*compileInto)[name] = value; } } @@ -2940,7 +2940,7 @@ void convert_room_to_native(Room ^room, RoomStruct &rs) for (size_t i = 0; i < rs.MessageCount; ++i) { RoomMessage ^newMessage = room->Messages[i]; - rs.Messages[i] = tcv->Convert(newMessage->Text); + rs.Messages[i] = tcv->ConvertTextProperty(newMessage->Text); if (newMessage->ShowAsSpeech) { rs.MessageInfos[i].DisplayAs = newMessage->CharacterID + 1; @@ -2966,7 +2966,7 @@ void convert_room_to_native(Room ^room, RoomStruct &rs) rs.Objects[i].Y = obj->StartY; rs.Objects[i].IsOn = obj->Visible; rs.Objects[i].Baseline = obj->Baseline; - rs.Objects[i].Name = tcv->Convert(obj->Description); + rs.Objects[i].Name = tcv->ConvertTextProperty(obj->Description); rs.Objects[i].Flags = 0; if (obj->UseRoomAreaScaling) rs.Objects[i].Flags |= OBJF_USEROOMSCALING; if (obj->UseRoomAreaLighting) rs.Objects[i].Flags |= OBJF_USEREGIONTINTS; @@ -2978,7 +2978,7 @@ void convert_room_to_native(Room ^room, RoomStruct &rs) for (size_t i = 0; i < rs.HotspotCount; ++i) { RoomHotspot ^hotspot = room->Hotspots[i]; - rs.Hotspots[i].Name = tcv->Convert(hotspot->Description); + rs.Hotspots[i].Name = tcv->ConvertTextProperty(hotspot->Description); rs.Hotspots[i].ScriptName = TextHelper::ConvertASCII(hotspot->Name); rs.Hotspots[i].WalkTo.X = hotspot->WalkToPoint.X; rs.Hotspots[i].WalkTo.Y = hotspot->WalkToPoint.Y;