From 620b915eda84391adcb86acd09c9240f0f73ef4f Mon Sep 17 00:00:00 2001 From: Colin Cornaby Date: Mon, 19 Sep 2022 21:38:26 -0700 Subject: [PATCH] Initial work on virtual display mode support Saving out display mode ratio of max res, and adding support for client to set resolution in response to window events Initial work on virtual display mode support Saving out display mode ratio of max res, and adding support for client to set resolution in response to window events --- Scripts/Python/xIniDisplay.py | 9 +- Scripts/Python/xOptionsMenu.py | 8 +- Sources/Plasma/Apps/plClient/plClient.cpp | 47 ++++- Sources/Plasma/Apps/plClient/plClient.h | 5 + Sources/Plasma/CoreLib/CMakeLists.txt | 2 + Sources/Plasma/CoreLib/hsIniHelper.cpp | 165 ++++++++++++++++++ Sources/Plasma/CoreLib/hsIniHelper.h | 90 ++++++++++ .../pfConsole/pfConsoleCommands.cpp | 5 + Sources/Tests/CoreTests/test_hsIniHelper.cpp | 135 ++++++++++++++ 9 files changed, 459 insertions(+), 7 deletions(-) create mode 100644 Sources/Plasma/CoreLib/hsIniHelper.cpp create mode 100644 Sources/Plasma/CoreLib/hsIniHelper.h create mode 100644 Sources/Tests/CoreTests/test_hsIniHelper.cpp diff --git a/Scripts/Python/xIniDisplay.py b/Scripts/Python/xIniDisplay.py index 6d14035559..163cef8861 100644 --- a/Scripts/Python/xIniDisplay.py +++ b/Scripts/Python/xIniDisplay.py @@ -62,9 +62,10 @@ kGraphicsShadows = "Graphics.Shadow.Enable" kGraphicsVerticalSync = "Graphics.EnableVSync" kGraphicsShadowQuality = "Graphics.Shadow.VisibleDistance" +kGraphicsOutputScale = "Graphics.OutputScale" -CmdList = [kGraphicsWidth, kGraphicsHeight, kGraphicsColorDepth, kGraphicsWindowed, kGraphicsTextureQuality, kGraphicsAntiAliasLevel, kGraphicsAnisotropicLevel, kGraphicsQualityLevel, kGraphicsShadows, kGraphicsVerticalSync, kGraphicsShadowQuality] -DefaultsList = ["800", "600", "32", "false", "2", "0", "0", "2", "true", "false", "0"] +CmdList = [kGraphicsWidth, kGraphicsHeight, kGraphicsColorDepth, kGraphicsWindowed, kGraphicsTextureQuality, kGraphicsAntiAliasLevel, kGraphicsAnisotropicLevel, kGraphicsQualityLevel, kGraphicsShadows, kGraphicsVerticalSync, kGraphicsShadowQuality, kGraphicsOutputScale] +DefaultsList = ["800", "600", "32", "false", "2", "0", "0", "2", "true", "false", "0", "100"] def ConstructFilenameAndPath(): global gFilenameAndPath @@ -120,9 +121,9 @@ def ReadIni(): ConstructFilenameAndPath() gIniFile.writeFile(gFilenameAndPath) -def SetGraphicsOptions(width, heigth, colordepth, windowed, texquality, aaLevel, anisoLevel, qualityLevel, useShadows, vsync, shadowqual): +def SetGraphicsOptions(width, heigth, colordepth, windowed, texquality, aaLevel, anisoLevel, qualityLevel, useShadows, vsync, shadowqual, outputScale): if gIniFile: - paramList = [width, heigth, colordepth, windowed, texquality, aaLevel, anisoLevel, qualityLevel, useShadows, vsync, shadowqual] + paramList = [width, heigth, colordepth, windowed, texquality, aaLevel, anisoLevel, qualityLevel, useShadows, vsync, shadowqual, outputScale] for idx in range(len(CmdList)): entry,junk = gIniFile.findByCommand(CmdList[idx]) val = str(paramList[idx]) diff --git a/Scripts/Python/xOptionsMenu.py b/Scripts/Python/xOptionsMenu.py index c40dfada7f..6d4f363cb1 100644 --- a/Scripts/Python/xOptionsMenu.py +++ b/Scripts/Python/xOptionsMenu.py @@ -443,6 +443,7 @@ gClickToTurn = "0" gAudioHack = 0 gCurrentReleaseNotes = "Welcome to Myst Online: Uru Live!" +gMaxVideoWidth = 0 class xOptionsMenu(ptModifier): "The Options dialog modifier" @@ -1488,6 +1489,7 @@ def ResetVideoToDefault(self): self.SetVidResField(vidRes) def InitVideoControlsGUI(self): + global gMaxVideoWidth xIniDisplay.ReadIni() opts = xIniDisplay.GetGraphicsOptions() @@ -1541,6 +1543,7 @@ def InitVideoControlsGUI(self): if not vidRes in vidResList: vidRes = vidResList[numRes-1] + gMaxVideoWidth = int(vidResList[numRes-1].split("x")[0]) for res in range(numRes): if vidRes == vidResList[res]: if numRes > 1: @@ -1644,8 +1647,9 @@ def WriteVideoControls(self, setMode = 0): gammaField = ptGUIControlKnob(GraphicsSettingsDlg.dialog.getControlFromTag(kGSDisplayGammaSlider)) gamma = gammaField.getValue() - - xIniDisplay.SetGraphicsOptions(width, height, colordepth, windowed, tex_quality, antialias, aniso, quality, shadows, vsyncstr, shadow_quality) + resolutionScale = int(round((width / gMaxVideoWidth) * 100)) + + xIniDisplay.SetGraphicsOptions(width, height, colordepth, windowed, tex_quality, antialias, aniso, quality, shadows, vsyncstr, shadow_quality, resolutionScale) xIniDisplay.WriteIni() self.setNewChronicleVar("gamma", gamma) diff --git a/Sources/Plasma/Apps/plClient/plClient.cpp b/Sources/Plasma/Apps/plClient/plClient.cpp index 9d0c19fed1..02cb2978cc 100644 --- a/Sources/Plasma/Apps/plClient/plClient.cpp +++ b/Sources/Plasma/Apps/plClient/plClient.cpp @@ -150,7 +150,6 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com #include "pfPython/cyMisc.h" #include "pfPython/cyPythonInterface.h" - #define MSG_LOADING_BAR // static hsVector3 gAbsDown(0,0,-1.f); @@ -221,6 +220,7 @@ plClient::~plClient() plClient::SetInstance(nullptr); delete fPageMgr; + delete fGraphicsIni; } template @@ -443,6 +443,7 @@ bool plClient::InitPipeline(hsWindowHndl display, uint32_t devType) hsG3DDeviceRecord *rec = (hsG3DDeviceRecord *)dmr.GetDevice(); +#if !PLASMA_DISPLAY_INDEPENDENT_VIDEO_MODES if(!plPipeline::fInitialPipeParams.Windowed) { // find our resolution if we're not in windowed mode @@ -467,6 +468,7 @@ bool plClient::InitPipeline(hsWindowHndl display, uint32_t devType) ISetGraphicsDefaults(); } } +#endif if(plPipeline::fInitialPipeParams.TextureQuality == -1) { @@ -1900,6 +1902,47 @@ void plClient::ResizeDisplayDevice(int Width, int Height, bool Windowed) IResizeNativeDisplayDevice(Width, Height, Windowed); } +void plClient::SetDisplayOptions(int Width, int Height, bool Windowed) +{ + fGraphicsIni->readFile(); + std::shared_ptr entry = fGraphicsIni->findByCommand("Graphics.Windowed"); + if (entry) { + entry->setValue(0, Windowed ? "true" : "false"); + } + + entry = fGraphicsIni->findByCommand("Graphics.OutputScale"); + if (entry) { + double scale = entry->getValue(0)->to_uint() / 100.0; + Width *= scale; + Height *= scale; + } + + entry = fGraphicsIni->findByCommand("Graphics.Width"); + if (entry) { + entry->setValue(0, std::to_string(Width)); + } + entry = fGraphicsIni->findByCommand("Graphics.Height"); + if (entry) { + entry->setValue(0, std::to_string(Height)); + } + fGraphicsIni->writeFile(); + + FlushGraphicsOptions(); +} + +void plClient::FlushGraphicsOptions() +{ + int Width = fGraphicsIni->findByCommand("Graphics.Width")->getValue(0)->to_int(); + int Height = fGraphicsIni->findByCommand("Graphics.Height")->getValue(0)->to_int(); + int ColorDepth = fGraphicsIni->findByCommand("Graphics.ColorDepth")->getValue(0)->to_int(); + int NumAASamples = fGraphicsIni->findByCommand("Graphics.AntiAliasAmount")->getValue(0)->to_int(); + int MaxAnisotropicSamples = fGraphicsIni->findByCommand("Graphics.AnisotropicLevel")->getValue(0)->to_int(); + bool VSync = (fGraphicsIni->findByCommand("Graphics.EnableVSync")->getValue(0) == "true"); + bool Windowed = (fGraphicsIni->findByCommand("Graphics.Windowed")->getValue(0) == "true"); + + ResetDisplayDevice(Width, Height, ColorDepth, Windowed, NumAASamples, MaxAnisotropicSamples); +} + void WriteBool(hsStream *stream, const char *name, bool on ) { char command[256]; @@ -1998,6 +2041,8 @@ void plClient::IDetectAudioVideoSettings() s.Close(); else IWriteDefaultGraphicsSettings(graphicsIniFile); + + fGraphicsIni = new hsIniFile(graphicsIniFile); } void plClient::IWriteDefaultAudioSettings(const plFileName& destFile) diff --git a/Sources/Plasma/Apps/plClient/plClient.h b/Sources/Plasma/Apps/plClient/plClient.h index dca8e1f659..1ce423212b 100644 --- a/Sources/Plasma/Apps/plClient/plClient.h +++ b/Sources/Plasma/Apps/plClient/plClient.h @@ -48,6 +48,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com #include "HeadSpin.h" #include "hsBitVector.h" +#include "hsIniHelper.h" #include "plFileSystem.h" #include @@ -157,6 +158,8 @@ class plClient : public hsKeyedObject plMessagePumpProc fMessagePumpProc; + hsIniFile *fGraphicsIni; + #ifndef PLASMA_EXTERNAL_RELEASE bool bPythonDebugConnected; #endif @@ -223,6 +226,7 @@ class plClient : public hsKeyedObject void IResizeNativeDisplayDevice(int width, int height, bool windowed); void IChangeResolution(int width, int height); void IUpdateProgressIndicator(plOperationProgress* progress); + void FlushGraphicsOptions(); public: @@ -310,6 +314,7 @@ class plClient : public hsKeyedObject void SetMessagePumpProc(plMessagePumpProc proc) { fMessagePumpProc = proc; } void ResetDisplayDevice(int Width, int Height, int ColorDepth, bool Windowed, int NumAASamples, int MaxAnisotropicSamples, bool VSync = false); void ResizeDisplayDevice(int Width, int Height, bool Windowed); + void SetDisplayOptions(int Width, int Height, bool Windowed); plAnimDebugList *fAnimDebugList; }; diff --git a/Sources/Plasma/CoreLib/CMakeLists.txt b/Sources/Plasma/CoreLib/CMakeLists.txt index ccf7c806ba..2eaf5ff05c 100644 --- a/Sources/Plasma/CoreLib/CMakeLists.txt +++ b/Sources/Plasma/CoreLib/CMakeLists.txt @@ -7,6 +7,7 @@ set(CoreLib_SOURCES hsExceptionStack.cpp hsFastMath.cpp hsGeometry3.cpp + hsIniHelper.cpp hsMatrix33.cpp hsMatrix44.cpp hsMemory.cpp @@ -48,6 +49,7 @@ set(CoreLib_HEADERS hsExceptionStack.h hsFastMath.h hsGeometry3.h + hsIniHelper.h hsLockGuard.h hsMatrix44.h hsMemory.h diff --git a/Sources/Plasma/CoreLib/hsIniHelper.cpp b/Sources/Plasma/CoreLib/hsIniHelper.cpp new file mode 100644 index 0000000000..f0766a8a92 --- /dev/null +++ b/Sources/Plasma/CoreLib/hsIniHelper.cpp @@ -0,0 +1,165 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +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 . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com + or by snail mail at: + Cyan Worlds, Inc. + 14617 N Newport Hwy + Mead, WA 99021 + +*==LICENSE==*/ + +#include "hsIniHelper.h" +#include "hsStringTokenizer.h" + +hsIniEntry::hsIniEntry(ST::string line): +fCommand(), fComment() { + if(line.size() == 0) { + fType = kBlankLine; + } else if(line[0] == '#') { + fType = kComment; + fComment = line.after_first('#'); + } else if(line == "\n") { + fType = kBlankLine; + } else { + fType = kCommandValue; + hsStringTokenizer tokenizer = hsStringTokenizer(line.c_str(), " "); + char *str; + int i = 0; + while((str = tokenizer.next())) { + if (i==0) { + fCommand = str; + } else { + fValues.push_back(str); + } + i++; + } + } +} + +void hsIniEntry::setValue(size_t idx, ST::string value) { + if (fValues.size() >= idx) { + fValues[idx] = value; + } else { + for (int i=fValues.size(); i hsIniEntry::getValue(size_t idx) { + if (fValues.size() < idx) { + return std::optional(); + } else { + return fValues[idx]; + } +} + + +hsIniFile::hsIniFile(plFileName filename) { + + this->filename = filename; + readFile(); +} + + +hsIniFile::hsIniFile(hsStream& stream) { + readStream(stream); +} + +void hsIniFile::readStream(hsStream& stream) { + ST::string line; + while(stream.ReadLn(line)) { + std::shared_ptr entry = std::make_shared(line); + fEntries.push_back(entry); + } +} + +void hsIniFile::writeFile() { + hsAssert(filename.GetSize() > 0, "writeFile requires contructor with filename"); + + hsBufferedStream s; + s.Open(filename, "w"); + writeStream(s); + s.Close(); +} + +void hsIniFile::readFile() { + hsAssert(filename.GetSize() > 0, "writeFile requires contructor with filename"); + + fEntries.clear(); + + hsBufferedStream s; + s.Open(filename); + readStream(s); + s.Close(); +} + +void hsIniFile::writeFile(plFileName filename) { + hsBufferedStream s; + s.Open(filename, "w"); + writeStream(s); + s.Close(); +} + +void hsIniFile::writeStream(hsStream& stream) { + for (std::shared_ptr entry: fEntries) { + switch (entry->fType) { + case hsIniEntry::kBlankLine: + stream.WriteSafeString("\n"); + break; + case hsIniEntry::kComment: + stream.WriteSafeString("#" + entry.get()->fComment + "\n"); + break; + case hsIniEntry::kCommandValue: + ST::string line = entry->fCommand; + for (ST::string value: entry->fValues) { + line += " " + value; + } + line += "\n"; + stream.WriteString(line); + break; + } + } +} + +std::shared_ptr hsIniFile::findByCommand(ST::string command) { + for (std::shared_ptr entry: fEntries) { + if(entry->fCommand == command) { + return entry; + } + } + return std::shared_ptr(nullptr); +} diff --git a/Sources/Plasma/CoreLib/hsIniHelper.h b/Sources/Plasma/CoreLib/hsIniHelper.h new file mode 100644 index 0000000000..b781221e05 --- /dev/null +++ b/Sources/Plasma/CoreLib/hsIniHelper.h @@ -0,0 +1,90 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +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 . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com + or by snail mail at: + Cyan Worlds, Inc. + 14617 N Newport Hwy + Mead, WA 99021 + +*==LICENSE==*/ + +#ifndef hsIniHelper_hpp +#define hsIniHelper_hpp + +#include +#include +#include +#include +#include "plFileSystem.h" +#include "hsStream.h" + +#endif /* hsIniHelper_hpp */ + +class hsIniFile; + +class hsIniEntry { + friend hsIniFile; +public: + + enum Type { + kBlankLine = 0, + kComment, + kCommandValue + }; + + Type fType; + ST::string fCommand; + ST::string fComment; + std::vector fValues; + void setValue(size_t idx, ST::string value); + std::optional getValue(size_t idx); + hsIniEntry(ST::string line); +}; + +class hsIniFile { +public: + std::vector> fEntries; + + hsIniFile(plFileName filename); + hsIniFile(hsStream& stream); + void writeFile(plFileName filename); + void writeFile(); + void writeStream(hsStream& stream); + std::shared_ptr findByCommand(ST::string command); + void readFile(); +private: + void readStream(hsStream& stream); + plFileName filename; +}; diff --git a/Sources/Plasma/FeatureLib/pfConsole/pfConsoleCommands.cpp b/Sources/Plasma/FeatureLib/pfConsole/pfConsoleCommands.cpp index 9f42cc4ade..ac72f51300 100644 --- a/Sources/Plasma/FeatureLib/pfConsole/pfConsoleCommands.cpp +++ b/Sources/Plasma/FeatureLib/pfConsole/pfConsoleCommands.cpp @@ -2069,6 +2069,11 @@ PF_CONSOLE_CMD( Graphics, EnableVSync, "bool b", "Init VerticalSync" ) plPipeline::fInitialPipeParams.VSync = (bool) params[0]; } +PF_CONSOLE_CMD( Graphics, OutputScale, "int s", "Init OutputScale" ) +{ + //plPipeline::fInitialPipeParams.VSync = (bool) params[0]; +} + PF_CONSOLE_CMD( Graphics, EnablePlanarReflections, "bool", "Enable the draw and update of planar reflections" ) { bool enable = (bool)params[0]; diff --git a/Sources/Tests/CoreTests/test_hsIniHelper.cpp b/Sources/Tests/CoreTests/test_hsIniHelper.cpp new file mode 100644 index 0000000000..6cd57a488d --- /dev/null +++ b/Sources/Tests/CoreTests/test_hsIniHelper.cpp @@ -0,0 +1,135 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +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 . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com + or by snail mail at: + Cyan Worlds, Inc. + 14617 N Newport Hwy + Mead, WA 99021 + +*==LICENSE==*/ + + +#include + +#include "hsIniHelper.h" +#include +#include + + +TEST(hsIniHelper, entry_comment) +{ + hsIniEntry entry("#This is a comment"); + EXPECT_EQ(entry.fType, hsIniEntry::Type::kComment); + EXPECT_STREQ(entry.fComment.c_str(), "This is a comment"); + EXPECT_EQ(entry.fValues.size(), 0); + EXPECT_STREQ(entry.fCommand.c_str(), ""); +} + +TEST(hsIniHelper, entry_blankLine) +{ + hsIniEntry entry("\n"); + EXPECT_EQ(entry.fType, hsIniEntry::Type::kBlankLine); + EXPECT_STREQ(entry.fComment.c_str(), ""); + EXPECT_EQ(entry.fValues.size(), 0); + EXPECT_STREQ(entry.fCommand.c_str(), ""); +} + +TEST(hsIniHelper, entry_command) +{ + hsIniEntry entry("Graphics.Height 1024"); + EXPECT_EQ(entry.fType, hsIniEntry::Type::kCommandValue); + EXPECT_STREQ(entry.fComment.c_str(), ""); + EXPECT_EQ(entry.fValues.size(), 1); + EXPECT_STREQ(entry.fValues[0].c_str(), "1024"); + EXPECT_STREQ(entry.fCommand.c_str(), "Graphics.Height"); +} + +TEST(hsIniHelper, entry_command_quoted) +{ + hsIniEntry entry("Graphics.Height \"1024 1024\""); + EXPECT_EQ(entry.fType, hsIniEntry::Type::kCommandValue); + EXPECT_STREQ(entry.fComment.c_str(), ""); + EXPECT_EQ(entry.fValues.size(), 1); + EXPECT_STREQ(entry.fValues[0].c_str(), "1024 1024"); + EXPECT_STREQ(entry.fCommand.c_str(), "Graphics.Height"); +} + +TEST(hsIniHelper, entry_stream_parse) +{ + hsRAMStream s; + std::string line = "Graphics.Height 1024\n"; + s.Write(line.length(), line.data()); + line = "Graphics.Width 768"; + s.Write(line.length(), line.data()); + s.Rewind(); + + hsIniFile file(s); + + std::shared_ptr heightEntry = file.findByCommand("Graphics.Height"); + EXPECT_NE(heightEntry, nullptr); + EXPECT_EQ(heightEntry->fType, hsIniEntry::kCommandValue); + EXPECT_EQ(heightEntry->fCommand, "Graphics.Height"); + EXPECT_EQ(heightEntry->fValues, std::vector({"1024"})); + + std::shared_ptr widthEntry = file.findByCommand("Graphics.Width"); + EXPECT_NE(widthEntry, nullptr); + EXPECT_EQ(widthEntry->fType, hsIniEntry::kCommandValue); + EXPECT_EQ(widthEntry->fCommand, "Graphics.Width"); + EXPECT_EQ(widthEntry->fValues, std::vector({"768"})); + + std::shared_ptr notAnEntry = file.findByCommand("NotACommand"); + EXPECT_EQ(notAnEntry, nullptr); +} + +TEST(hsIniHelper, entry_stream_mutate) +{ + hsRAMStream s; + std::string line = "Graphics.Height 1024\n"; + s.Write(line.length(), line.data()); + s.Rewind(); + + hsIniFile file(s); + + std::shared_ptr heightEntry = file.findByCommand("Graphics.Height"); + EXPECT_NE(heightEntry, nullptr); + EXPECT_EQ(heightEntry->fType, hsIniEntry::kCommandValue); + EXPECT_EQ(heightEntry->fCommand, "Graphics.Height"); + EXPECT_EQ(heightEntry->fValues, std::vector({"1024"})); + + heightEntry->setValue(0, "2048"); + + heightEntry = file.findByCommand("Graphics.Height"); + EXPECT_EQ(heightEntry->fValues, std::vector({"2048"})); +}