From 9df8d56193cc9ccdbffb33a9d4e4847a3089c4ce Mon Sep 17 00:00:00 2001 From: Colin Cornaby Date: Mon, 19 Sep 2022 21:15:02 -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 --- Scripts/Python/xIniDisplay.py | 9 +- Scripts/Python/xOptionsMenu.py | 12 +- 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 | 130 ++++++++++++++++++ Sources/Plasma/CoreLib/hsIniHelper.h | 55 ++++++++ .../pfConsole/pfConsoleCommands.cpp | 5 + .../PubUtilLib/plInputCore/plInputDevice.cpp | 19 ++- .../PubUtilLib/plInputCore/plInputDevice.h | 2 + Sources/Tests/CoreTests/test_hsIniHelper.cpp | 100 ++++++++++++++ 11 files changed, 372 insertions(+), 14 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..f318c4e206 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() @@ -1540,7 +1542,10 @@ def InitVideoControlsGUI(self): if not vidRes in vidResList: vidRes = vidResList[numRes-1] - + + gMaxVideoWidth = int(vidResList[numRes-1].split("x")[0]) + print("Largest video width is " + str(gMaxVideoWidth)) + for res in range(numRes): if vidRes == vidResList[res]: if numRes > 1: @@ -1644,8 +1649,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..7e5de7b286 --- /dev/null +++ b/Sources/Plasma/CoreLib/hsIniHelper.cpp @@ -0,0 +1,130 @@ +// +// hsIniHelper.cpp +// CoreLib +// +// Created by Colin Cornaby on 8/29/22. +// + +#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..7af864b222 --- /dev/null +++ b/Sources/Plasma/CoreLib/hsIniHelper.h @@ -0,0 +1,55 @@ +// +// hsIniHelper.hpp +// CoreLib +// +// Created by Colin Cornaby on 8/29/22. +// + +#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/Plasma/PubUtilLib/plInputCore/plInputDevice.cpp b/Sources/Plasma/PubUtilLib/plInputCore/plInputDevice.cpp index fff70f841e..86cb6f0dac 100644 --- a/Sources/Plasma/PubUtilLib/plInputCore/plInputDevice.cpp +++ b/Sources/Plasma/PubUtilLib/plInputCore/plInputDevice.cpp @@ -227,6 +227,7 @@ bool plMouseDevice::bCursorOverride = false; bool plMouseDevice::bInverted = false; float plMouseDevice::fWidth = BASE_WIDTH; float plMouseDevice::fHeight = BASE_HEIGHT; +float plMouseDevice::fScale = 1.0f; plMouseDevice* plMouseDevice::fInstance = nullptr; plMouseDevice::plMouseDevice() @@ -253,6 +254,12 @@ void plMouseDevice::SetDisplayResolution(float Width, float Height) IUpdateCursorSize(); } +void plMouseDevice::SetDisplayScale(float Scale) +{ + fScale = Scale; + IUpdateCursorSize(); +} + void plMouseDevice::CreateCursor( const char* cursor ) { if (fCursor == nullptr) @@ -276,7 +283,7 @@ void plMouseDevice::IUpdateCursorSize() if(fCursor) { // set the size of the cursor based on resolution. - fCursor->SetSize( 2*fCursor->GetMipmap()->GetWidth()/fWidth, 2*fCursor->GetMipmap()->GetHeight()/fHeight ); + fCursor->SetSize( fScale * 2*fCursor->GetMipmap()->GetWidth()/fWidth, fScale * 2*fCursor->GetMipmap()->GetHeight()/fHeight ); } } @@ -285,7 +292,7 @@ void plMouseDevice::AddNameToCursor(const ST::string& name) if (fInstance && !name.empty()) { plDebugText &txt = plDebugText::Instance(); - txt.DrawString(fInstance->fWXPos + 12 ,fInstance->fWYPos - 7,name); + txt.DrawString(fInstance->fWXPos + 12 * fScale, fInstance->fWYPos - txt.GetFontHeight() / 2,name); } } void plMouseDevice::AddCCRToCursor() @@ -293,7 +300,7 @@ void plMouseDevice::AddCCRToCursor() if (fInstance) { plDebugText &txt = plDebugText::Instance(); - txt.DrawString(fInstance->fWXPos + 12, fInstance->fWYPos - 17, "CCR"); + txt.DrawString(fInstance->fWXPos + 12 * fScale, fInstance->fWYPos - 17, "CCR"); } } void plMouseDevice::AddIDNumToCursor(uint32_t idNum) @@ -303,7 +310,7 @@ void plMouseDevice::AddIDNumToCursor(uint32_t idNum) plDebugText &txt = plDebugText::Instance(); char str[256]; sprintf(str, "%d",idNum); - txt.DrawString(fInstance->fWXPos + 12 ,fInstance->fWYPos + 3,str); + txt.DrawString(fInstance->fWXPos + 12 * fScale,fInstance->fWYPos + 3,str); } } @@ -483,7 +490,7 @@ bool plMouseDevice::MsgReceive(plMessage* msg) fXPos = pXMsg->fX; SetCursorX(fXPos); - fWXPos = pXMsg->fWx; + fWXPos = pXMsg->fWx * fScale; return true; } @@ -520,7 +527,7 @@ bool plMouseDevice::MsgReceive(plMessage* msg) else fYPos = pYMsg->fY; - fWYPos = pYMsg->fWy; + fWYPos = pYMsg->fWy * fScale; SetCursorY(fYPos); return true; diff --git a/Sources/Plasma/PubUtilLib/plInputCore/plInputDevice.h b/Sources/Plasma/PubUtilLib/plInputCore/plInputDevice.h index 20313de953..18c3baa3dd 100644 --- a/Sources/Plasma/PubUtilLib/plInputCore/plInputDevice.h +++ b/Sources/Plasma/PubUtilLib/plInputCore/plInputDevice.h @@ -175,6 +175,7 @@ class plMouseDevice : public plInputDevice uint32_t GetButtonState() { return fButtonState; } float GetCursorOpacity() { return fOpacity; } void SetDisplayResolution(float Width, float Height); + void SetDisplayScale(float Scale); bool MsgReceive(plMessage* msg) override; @@ -223,6 +224,7 @@ class plMouseDevice : public plInputDevice static bool bCursorOverride; static bool bInverted; static float fWidth, fHeight; + static float fScale; }; diff --git a/Sources/Tests/CoreTests/test_hsIniHelper.cpp b/Sources/Tests/CoreTests/test_hsIniHelper.cpp new file mode 100644 index 0000000000..2593e727c4 --- /dev/null +++ b/Sources/Tests/CoreTests/test_hsIniHelper.cpp @@ -0,0 +1,100 @@ +// +// test_hsIniHelper.cpp +// test_CoreLib +// +// Created by Colin Cornaby on 8/29/22. +// + + +#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"})); +}