Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/devkit/data/models/GameAssets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,14 @@ void GameAssets::ReloadAssets(const std::vector<ra::data::models::AssetModelBase
pTokenizer.Consume('M');
break;

case 'B':
if (pTokenizer.Match("BadgeDir=")) {
const auto pLastDirectory = pTokenizer.ReadTo('\n');
auto& pLocalBadges = ra::services::ServiceLocator::GetMutable<ra::context::IGameContext>().LocalBadges();
pLocalBadges.SetLastDirectory(ra::util::String::Widen(pLastDirectory));
}
break;

default:
continue;
}
Expand Down Expand Up @@ -484,6 +492,13 @@ void GameAssets::SaveAssets(const std::vector<ra::data::models::AssetModelBase*>
pData->WriteLine(pPrimarySubset->GetTitle());
}

const auto& sLastBadgeDirectory = pGameContext.LocalBadges().GetLastDirectory();
if (!sLastBadgeDirectory.empty())
{
pData->Write("BadgeDir=");
pData->WriteLine(sLastBadgeDirectory);
}

bool bHasDeleted = false;
for (auto& pAsset : *this)
{
Expand Down
2 changes: 2 additions & 0 deletions src/devkit/data/models/LocalBadgesModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace ra {
namespace data {
namespace models {

const StringModelProperty LocalBadgesModel::LastDirectoryProperty("LocalBadgesModel", "LastDirectory", L"");

LocalBadgesModel::LocalBadgesModel() noexcept
{
GSL_SUPPRESS_F6 SetValue(TypeProperty, ra::etoi(AssetType::LocalBadges));
Expand Down
15 changes: 15 additions & 0 deletions src/devkit/data/models/LocalBadgesModel.hh
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ public:
/// </summary>
void Commit(const std::wstring& sPreviousBadgeName, const std::wstring& sNewBadgeName);

/// <summary>
/// The <see cref="ModelProperty" /> for the last directory from which a badge was selected.
/// </summary>
static const StringModelProperty LastDirectoryProperty;

/// <summary>
/// Gets the last directory from which a badge was selected.
/// </summary>
const std::wstring& GetLastDirectory() const { return GetValue(LastDirectoryProperty); }

/// <summary>
/// Sets the last directory from which a badge was selected.
/// </summary>
void SetLastDirectory(const std::wstring& sValue) { SetValue(LastDirectoryProperty, sValue); }

private:
struct BadgeReferenceCount
{
Expand Down
5 changes: 5 additions & 0 deletions src/devkit/services/IFileSystem.hh
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ public:
/// <remarks>Will create the file if it doesn't already exist.</remarks>
virtual std::unique_ptr<TextWriter> AppendTextFile(const std::wstring& sPath) const = 0;

/// <summary>
/// Gets the directory portion of a path.
/// </summary>
virtual std::wstring GetDirectory(const std::wstring& sPath) const = 0;

/// <summary>
/// Gets the filename portion of a path.
/// </summary>
Expand Down
14 changes: 14 additions & 0 deletions src/devkit/util/Tokenizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@
namespace ra {
namespace util {

bool Tokenizer::Match(const std::string_view sText) noexcept
{
if (m_nPosition + sText.length() <= m_sString.length())
{
if (m_sString.compare(m_nPosition, sText.length(), sText) == 0)
{
m_nPosition += sText.length();
return true;
}
}

return false;
}

std::string Tokenizer::ReadQuotedString()
{
std::string sString;
Expand Down
7 changes: 7 additions & 0 deletions src/devkit/util/Tokenizer.hh
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ public:
++m_nPosition;
}

/// <summary>
/// Attempts to match the specified string.
/// </summary>
/// <returns><c>true</c> if the next set of characters matches the provided text.</returns>
/// <remarks>Consumes the text if matched.</remarks>
bool Match(const std::string_view sText) noexcept;

/// <summary>
/// Advances the cursor to the next occurrence of the specified character, or the end of the string if no
/// occurrences are found and returns a string containing all of the characters advanced over.
Expand Down
9 changes: 9 additions & 0 deletions src/services/impl/WindowsFileSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ const std::wstring& WindowsFileSystem::MakeAbsolute(std::wstring& sBuffer, const
return sBuffer;
}

std::wstring WindowsFileSystem::GetDirectory(const std::wstring& sPath) const
{
const auto nIndex = sPath.find_last_of(L"/\\");
if (nIndex != std::string::npos)
return std::wstring(sPath, 0, nIndex);

return sPath;
}

std::wstring WindowsFileSystem::GetFileName(const std::wstring& sPath) const
{
const auto nIndex = sPath.find_last_of(L"/\\");
Expand Down
1 change: 1 addition & 0 deletions src/services/impl/WindowsFileSystem.hh
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public:
std::unique_ptr<TextWriter> CreateTextFile(const std::wstring& sPath) const override;
std::unique_ptr<TextWriter> AppendTextFile(const std::wstring& sPath) const override;

std::wstring GetDirectory(const std::wstring& sPath) const override;
std::wstring GetFileName(const std::wstring& sPath) const override;
std::wstring GetExtension(const std::wstring& sPath) const override;
std::wstring RemoveExtension(const std::wstring& sPath) const override;
Expand Down
15 changes: 15 additions & 0 deletions src/ui/viewmodels/AssetEditorViewModel.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "AssetEditorViewModel.hh"

#include "data\context\GameContext.hh"

#include "data\models\AchievementModel.hh"
#include "data\models\LeaderboardModel.hh"

Expand Down Expand Up @@ -103,14 +105,27 @@ AssetEditorViewModel::~AssetEditorViewModel()

void AssetEditorViewModel::SelectBadgeFile()
{
auto& pLocalBadges = ra::services::ServiceLocator::GetMutable<ra::data::context::GameContext>().LocalBadges();
const auto sOldLastDirectory = pLocalBadges.GetLastDirectory();
ui::viewmodels::FileDialogViewModel vmFile;
vmFile.AddFileType(L"Image Files", L"*.png;*.gif;*.jpg;*.jpeg");
vmFile.SetDefaultExtension(L"png");
vmFile.SetInitialDirectory(sOldLastDirectory);
if (vmFile.ShowOpenFileDialog(*this) != DialogResult::OK)
return;

const auto& pFileName = vmFile.GetFileName();
auto& pFileSystemService = ra::services::ServiceLocator::GetMutable<ra::services::IFileSystem>();

const auto sNewLastDirectory = pFileSystemService.GetDirectory(pFileName);
if (sNewLastDirectory != sOldLastDirectory)
{
pLocalBadges.SetLastDirectory(sNewLastDirectory);
std::vector<ra::data::models::AssetModelBase*> vAssetsToSave;
vAssetsToSave.push_back(&pLocalBadges);
ra::services::ServiceLocator::GetMutable<ra::data::context::GameContext>().Assets().SaveAssets(vAssetsToSave);
}

const auto pFile = pFileSystemService.OpenTextFile(pFileName);
if (!pFile)
{
Expand Down
5 changes: 4 additions & 1 deletion tests/devkit/data/models/GameAssets_Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ TEST_CLASS(GameAssets_Tests)

// gameAssets is not the same instance as mockGameContext.Assets, so manually update the badge reference count
auto& pLocalBadges = gameAssets.mockGameContext.LocalBadges();
pLocalBadges.SetLastDirectory(L"C:\\Games\\Images");
pLocalBadges.AddReference(L"local\\22-ABCDE.png", false);

// simulate the pre-commit of the achievement by converting the reference from uncommitted to committed
Expand All @@ -262,6 +263,7 @@ TEST_CLASS(GameAssets_Tests)
gameAssets.ReloadAsset(AssetType::Achievement, GameAssets::FirstLocalId);

const auto& sExpected = ra::util::String::Printf("0.0.0.0\nGame Title\n"
"BadgeDir=C:\\Games\\Images\n"
"%u:\"1=1\":Temp:Temp::::Authl:10:::::\"local\\\\22-ABCDE.png\"\n", GameAssets::FirstLocalId);
Assert::AreEqual(sExpected, gameAssets.GetUserFile());
}
Expand Down Expand Up @@ -455,7 +457,7 @@ TEST_CLASS(GameAssets_Tests)
{
GameAssetsHarness gameAssets;
gameAssets.mockGameContext.SetGameId(22);
gameAssets.MockUserFileContents(
gameAssets.MockUserFileContents("BadgeDir=C:\\BadgeImages\n"
"111000001:\"0xH2345=0\":Test2:::::User:0:0:0:::local\\22-A.png\n");

gameAssets.ReloadAllAssets();
Expand All @@ -467,6 +469,7 @@ TEST_CLASS(GameAssets_Tests)

const auto& pLocalBadges = gameAssets.mockGameContext.LocalBadges();
Assert::AreEqual(1, pLocalBadges.GetReferenceCount(L"local\\22-A.png", true));
Assert::AreEqual(std::wstring(L"C:\\BadgeImages"), pLocalBadges.GetLastDirectory());
}

TEST_METHOD(TestMergeLocalAssetsTwoLocalAchievementsWithSubsetInfo)
Expand Down
6 changes: 6 additions & 0 deletions tests/devkit/data/util/TriggerValidation_Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ TEST_CLASS(TriggerValidation_Tests)
// chained value note
AssertValidation("A:0xH0008_1=6", L""); // 1 is not an address so it shouldn't matter that it doesn't have a note
}

TEST_METHOD(TestRecallConstantAcrossAlts)
{
AssertValidation("SQ:0xX00aa4968=4_K:11160468_I:{recall}_N:0xX00000000!=0_M:0=1.3."
"SK:11160468_I:{recall}_N:0xX00000000!=0_R:0=1", L"");
}
};

} // namespace tests
Expand Down
9 changes: 9 additions & 0 deletions tests/devkit/services/mocks/MockFileSystem.hh
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,15 @@ public:
return std::unique_ptr<TextWriter>(pWriter.release());
}

std::wstring GetDirectory(const std::wstring& sPath) const override
{
const auto nIndex = sPath.find_last_of(L"/\\");
if (nIndex != std::string::npos)
return std::wstring(sPath, 0, nIndex);

return sPath;
}

std::wstring GetFileName(const std::wstring& sPath) const override
{
const auto nIndex = sPath.find_last_of(L"/\\");
Expand Down
19 changes: 19 additions & 0 deletions tests/devkit/util/Tokenizer_Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,25 @@ TEST_CLASS(Tokenizer_Tests)
Assert::IsFalse(tokenizer.Consume('d'));
Assert::IsFalse(tokenizer.Consume('\0'));
}

TEST_METHOD(TestMatch)
{
std::string input("abcdefgh");
Tokenizer tokenizer(input);

Assert::IsFalse(tokenizer.Match("bcd")); // first character is 'a'
Assert::IsTrue(tokenizer.Match("abc"));
Assert::AreEqual('d', tokenizer.PeekChar());
Assert::IsFalse(tokenizer.Match("abc"));
Assert::IsFalse(tokenizer.Match("den"));
Assert::IsTrue(tokenizer.Match("def"));
Assert::AreEqual('g', tokenizer.PeekChar());
Assert::IsFalse(tokenizer.Match("ghi"));
Assert::IsTrue(tokenizer.Match("gh"));
Assert::IsTrue(tokenizer.EndOfString());
Assert::IsFalse(tokenizer.Match("i"));
Assert::IsTrue(tokenizer.Match(""));
}
};


Expand Down
47 changes: 47 additions & 0 deletions tests/ui/viewmodels/AssetEditorViewModel_Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@
#include "tests\data\DataAsserts.hh"
#include "tests\ui\viewmodels\TriggerConditionAsserts.hh"

#include "tests\devkit\context\mocks\MockDevKitContext.hh"
#include "tests\devkit\context\mocks\MockEmulatorMemoryContext.hh"
#include "tests\devkit\context\mocks\MockRcClient.hh"
#include "tests\devkit\context\mocks\MockUserContext.hh"
#include "tests\devkit\services\mocks\MockClock.hh"
#include "tests\devkit\services\mocks\MockFileSystem.hh"
#include "tests\devkit\services\mocks\MockLocalStorage.hh"
#include "tests\devkit\services\mocks\MockLogger.hh"
#include "tests\devkit\services\mocks\MockThreadPool.hh"
#include "tests\devkit\testutil\AchievementAsserts.hh"
#include "tests\devkit\testutil\AssetAsserts.hh"
Expand Down Expand Up @@ -72,13 +75,16 @@ TEST_CLASS(AssetEditorViewModel_Tests)
AssetEditorViewModelHarness(AssetEditorViewModelHarness&&) noexcept = delete;
AssetEditorViewModelHarness& operator=(AssetEditorViewModelHarness&&) noexcept = delete;

ra::context::mocks::MockDevKitContext mockDevKitContext;
ra::context::mocks::MockEmulatorMemoryContext mockEmulatorMemoryContext;
ra::context::mocks::MockRcClient mockRcClient;
ra::context::mocks::MockUserContext mockUserContext;
ra::services::mocks::MockAchievementRuntime mockRuntime;
ra::services::mocks::MockClock mockClock;
ra::services::mocks::MockConfiguration mockConfiguration;
ra::services::mocks::MockFileSystem mockFileSystem;
ra::services::mocks::MockLocalStorage mockLocalStorage;
ra::services::mocks::MockLogger mockLogger;
ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
ra::data::context::mocks::MockGameContext mockGameContext;
ra::ui::mocks::MockDesktop mockDesktop;
Expand Down Expand Up @@ -2411,6 +2417,47 @@ TEST_CLASS(AssetEditorViewModel_Tests)
Assert::AreEqual(std::wstring(L"File does not appear to be a valid jpg image."), sMessage);
}

TEST_METHOD(TestSelectBadgeFileRemembersLastDirectory)
{
AssetEditorViewModelHarness editor;
AchievementModel achievement;
editor.LoadAsset(&achievement);

editor.mockGameContext.SetActiveGameId(1);
editor.mockGameContext.LocalBadges().SetLastDirectory(L"C:\\Badges");

bool bDialogSeen = false;
editor.mockDesktop.ExpectWindow<ra::ui::viewmodels::FileDialogViewModel>(
[&bDialogSeen](ra::ui::viewmodels::FileDialogViewModel& vmFileDialog)
{
bDialogSeen = true;

Assert::AreEqual(std::wstring(L"C:\\Badges"), vmFileDialog.GetInitialDirectory());

vmFileDialog.SetFileName(L"C:\\Badges\\Game1\\image.png");
return DialogResult::OK;
});

std::wstring sMessage;
editor.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>(
[&sMessage](ra::ui::viewmodels::MessageBoxViewModel& vmMessage)
{
sMessage = vmMessage.GetMessage();
return DialogResult::OK;
});

const std::string sFileContents("\x89PNG\x0D\x0A\x1A\x0D\x00\x00\x00\x0DIHDR", 16);
editor.mockFileSystem.MockFile(L"C:\\image.png", sFileContents);

editor.SelectBadgeFile();

Assert::AreEqual(std::wstring(L"C:\\Badges\\Game1"), editor.mockGameContext.LocalBadges().GetLastDirectory());
Assert::IsTrue(bDialogSeen);

const auto pUserFile = editor.mockLocalStorage.GetStoredData(ra::services::StorageItemType::UserAchievements, L"1");
Assert::AreEqual(std::string("0.0.0.0\nGame Title\nBadgeDir=C:\\Badges\\Game1\n"), pUserFile);
}

TEST_METHOD(TestChangingGroupUpdatesDebugHighlights)
{
AssetEditorViewModelHarness editor;
Expand Down
Loading