From 3975cc3ada037187c0315c61dee566c2c3f49711 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 20 Jul 2025 02:44:09 +0200 Subject: [PATCH 1/2] case insensitive cheat identifier --- lib/modding/IdentifierStorage.cpp | 53 ++++++++++++++++---- lib/modding/IdentifierStorage.h | 13 ++--- server/processors/PlayerMessageProcessor.cpp | 6 +-- 3 files changed, 52 insertions(+), 20 deletions(-) diff --git a/lib/modding/IdentifierStorage.cpp b/lib/modding/IdentifierStorage.cpp index d71afbe98..7cc7ec2a0 100644 --- a/lib/modding/IdentifierStorage.cpp +++ b/lib/modding/IdentifierStorage.cpp @@ -102,7 +102,7 @@ void CIdentifierStorage::requestIdentifier(const ObjectCallback & callback) cons resolveIdentifier(callback); } -CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameWithType(const std::string & scope, const std::string & fullName, const std::function & callback, bool optional) +CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameWithType(const std::string & scope, const std::string & fullName, const std::function & callback, bool optional, bool caseSensitive) { assert(!scope.empty()); @@ -120,10 +120,11 @@ CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameW result.callback = callback; result.optional = optional; result.dynamicType = true; + result.caseSensitive = caseSensitive; return result; } -CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameAndType(const std::string & scope, const std::string & type, const std::string & fullName, const std::function & callback, bool optional, bool bypassDependenciesCheck) +CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameAndType(const std::string & scope, const std::string & type, const std::string & fullName, const std::function & callback, bool optional, bool bypassDependenciesCheck, bool caseSensitive) { assert(!scope.empty()); @@ -150,6 +151,7 @@ CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameA result.optional = optional; result.bypassDependenciesCheck = bypassDependenciesCheck; result.dynamicType = false; + result.caseSensitive = caseSensitive; return result; } @@ -199,36 +201,36 @@ void CIdentifierStorage::tryRequestIdentifier(const std::string & type, const Js requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, true, false)); } -std::optional CIdentifierStorage::getIdentifier(const std::string & scope, const std::string & type, const std::string & name, bool silent) const +std::optional CIdentifierStorage::getIdentifier(const std::string & scope, const std::string & type, const std::string & name, bool silent, bool caseSensitive) const { //assert(state != ELoadingState::LOADING); - auto options = ObjectCallback::fromNameAndType(scope, type, name, std::function(), silent, false); + auto options = ObjectCallback::fromNameAndType(scope, type, name, std::function(), silent, false, caseSensitive); return getIdentifierImpl(options, silent); } -std::optional CIdentifierStorage::getIdentifier(const std::string & type, const JsonNode & name, bool silent) const +std::optional CIdentifierStorage::getIdentifier(const std::string & type, const JsonNode & name, bool silent, bool caseSensitive) const { assert(state != ELoadingState::LOADING); - auto options = ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), std::function(), silent, false); + auto options = ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), std::function(), silent, false, caseSensitive); return getIdentifierImpl(options, silent); } -std::optional CIdentifierStorage::getIdentifier(const JsonNode & name, bool silent) const +std::optional CIdentifierStorage::getIdentifier(const JsonNode & name, bool silent, bool caseSensitive) const { assert(state != ELoadingState::LOADING); - auto options = ObjectCallback::fromNameWithType(name.getModScope(), name.String(), std::function(), silent); + auto options = ObjectCallback::fromNameWithType(name.getModScope(), name.String(), std::function(), silent, caseSensitive); return getIdentifierImpl(options, silent); } -std::optional CIdentifierStorage::getIdentifier(const std::string & scope, const std::string & fullName, bool silent) const +std::optional CIdentifierStorage::getIdentifier(const std::string & scope, const std::string & fullName, bool silent, bool caseSensitive) const { assert(state != ELoadingState::LOADING); - auto options = ObjectCallback::fromNameWithType(scope, fullName, std::function(), silent); + auto options = ObjectCallback::fromNameWithType(scope, fullName, std::function(), silent, caseSensitive); return getIdentifierImpl(options, silent); } @@ -343,6 +345,34 @@ void CIdentifierStorage::registerObject(const std::string & scope, const std::st } } +template +std::pair +caseInsensitiveEqualRange(const MultiMap& mmap, const std::string& key) +{ + using ConstIt = typename MultiMap::const_iterator; + + std::string loweredKey = boost::algorithm::to_lower_copy(key); + + ConstIt first = mmap.end(); + ConstIt last = mmap.end(); + + for (ConstIt it = mmap.begin(); it != mmap.end(); ++it) + { + std::string loweredItKey = boost::algorithm::to_lower_copy(it->first); + + if (loweredItKey == loweredKey) + { + if (first == mmap.end()) + first = it; + last = std::next(it); + } else if (first != mmap.end()) + // We've already found the matching range and now it's over + break; + } + + return { first, last }; +} + std::vector CIdentifierStorage::getPossibleIdentifiers(const ObjectCallback & request) const { std::set allowedScopes; @@ -410,7 +440,8 @@ std::vector CIdentifierStorage::getPossibleIdent std::string fullID = request.type + '.' + request.name; - auto entries = registeredObjects.equal_range(fullID); + auto entries = request.caseSensitive ? registeredObjects.equal_range(fullID) : caseInsensitiveEqualRange(registeredObjects, fullID); + if (entries.first != entries.second) { std::vector locatedIDs; diff --git a/lib/modding/IdentifierStorage.h b/lib/modding/IdentifierStorage.h index 51ab94c1f..efc89b8c4 100644 --- a/lib/modding/IdentifierStorage.h +++ b/lib/modding/IdentifierStorage.h @@ -34,12 +34,13 @@ class DLL_LINKAGE CIdentifierStorage bool optional; bool bypassDependenciesCheck; bool dynamicType; + bool caseSensitive; /// Builds callback from identifier in form "targetMod:type.name" - static ObjectCallback fromNameWithType(const std::string & scope, const std::string & fullName, const std::function & callback, bool optional); + static ObjectCallback fromNameWithType(const std::string & scope, const std::string & fullName, const std::function & callback, bool optional, bool caseSensitive = true); /// Builds callback from identifier in form "targetMod:name" - static ObjectCallback fromNameAndType(const std::string & scope, const std::string & type, const std::string & fullName, const std::function & callback, bool optional, bool bypassDependenciesCheck); + static ObjectCallback fromNameAndType(const std::string & scope, const std::string & type, const std::string & fullName, const std::function & callback, bool optional, bool bypassDependenciesCheck, bool caseSensitive = true); private: ObjectCallback() = default; @@ -97,10 +98,10 @@ public: void tryRequestIdentifier(const std::string & type, const JsonNode & name, const std::function & callback) const; /// get identifier immediately. If identifier is not know and not silent call will result in error message - std::optional getIdentifier(const std::string & scope, const std::string & type, const std::string & name, bool silent = false) const; - std::optional getIdentifier(const std::string & type, const JsonNode & name, bool silent = false) const; - std::optional getIdentifier(const JsonNode & name, bool silent = false) const; - std::optional getIdentifier(const std::string & scope, const std::string & fullName, bool silent = false) const; + std::optional getIdentifier(const std::string & scope, const std::string & type, const std::string & name, bool silent = false, bool caseSensitive = true) const; + std::optional getIdentifier(const std::string & type, const JsonNode & name, bool silent = false, bool caseSensitive = true) const; + std::optional getIdentifier(const JsonNode & name, bool silent = false, bool caseSensitive = true) const; + std::optional getIdentifier(const std::string & scope, const std::string & fullName, bool silent = false, bool caseSensitive = true) const; /// registers new object void registerObject(const std::string & scope, const std::string & type, const std::string & name, si32 identifier); diff --git a/server/processors/PlayerMessageProcessor.cpp b/server/processors/PlayerMessageProcessor.cpp index 07bb9911a..ab7b24def 100644 --- a/server/processors/PlayerMessageProcessor.cpp +++ b/server/processors/PlayerMessageProcessor.cpp @@ -428,7 +428,7 @@ void PlayerMessageProcessor::cheatGiveArmy(PlayerColor player, const CGHeroInsta { } - std::optional creatureId = LIBRARY->identifiers()->getIdentifier(ModScope::scopeGame(), "creature", creatureIdentifier, false); + std::optional creatureId = LIBRARY->identifiers()->getIdentifier(ModScope::scopeGame(), "creature", creatureIdentifier, false, false); if(creatureId.has_value()) { @@ -469,7 +469,7 @@ void PlayerMessageProcessor::cheatGiveArtifacts(PlayerColor player, const CGHero { for (auto const & word : words) { - auto artID = LIBRARY->identifiers()->getIdentifier(ModScope::scopeGame(), "artifact", word, false); + auto artID = LIBRARY->identifiers()->getIdentifier(ModScope::scopeGame(), "artifact", word, false, false); if(artID && LIBRARY->arth->objects[*artID]) gameHandler->giveHeroNewArtifact(hero, ArtifactID(*artID), ArtifactPosition::FIRST_AVAILABLE); } @@ -728,7 +728,7 @@ void PlayerMessageProcessor::cheatSkill(PlayerColor player, const CGHeroInstance return; } - std::optional skillId = LIBRARY->identifiers()->getIdentifier(ModScope::scopeGame(), "skill", identifier, false); + std::optional skillId = LIBRARY->identifiers()->getIdentifier(ModScope::scopeGame(), "skill", identifier, false, false); if(!skillId.has_value()) return; From 70c84910bdb325bc17ecce5f33fb5b6d42091676 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 31 Jul 2025 00:46:43 +0200 Subject: [PATCH 2/2] code review --- lib/modding/IdentifierStorage.cpp | 70 +++++++------------- lib/modding/IdentifierStorage.h | 14 ++-- server/processors/PlayerMessageProcessor.cpp | 6 +- 3 files changed, 36 insertions(+), 54 deletions(-) diff --git a/lib/modding/IdentifierStorage.cpp b/lib/modding/IdentifierStorage.cpp index 7cc7ec2a0..93fa074d5 100644 --- a/lib/modding/IdentifierStorage.cpp +++ b/lib/modding/IdentifierStorage.cpp @@ -157,22 +157,22 @@ CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameA void CIdentifierStorage::requestIdentifier(const std::string & scope, const std::string & type, const std::string & name, const std::function & callback) const { - requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, false, false)); + requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, false, false, true)); } void CIdentifierStorage::requestIdentifier(const std::string & scope, const std::string & fullName, const std::function & callback) const { - requestIdentifier(ObjectCallback::fromNameWithType(scope, fullName, callback, false)); + requestIdentifier(ObjectCallback::fromNameWithType(scope, fullName, callback, false, true)); } void CIdentifierStorage::requestIdentifier(const std::string & type, const JsonNode & name, const std::function & callback) const { - requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, false, false)); + requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, false, false, true)); } void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::function & callback) const { - requestIdentifier(ObjectCallback::fromNameWithType(name.getModScope(), name.String(), callback, false)); + requestIdentifier(ObjectCallback::fromNameWithType(name.getModScope(), name.String(), callback, false, true)); } void CIdentifierStorage::requestIdentifierIfNotNull(const std::string & type, const JsonNode & name, const std::function & callback) const @@ -183,54 +183,60 @@ void CIdentifierStorage::requestIdentifierIfNotNull(const std::string & type, co void CIdentifierStorage::requestIdentifierIfFound(const std::string & type, const JsonNode & name, const std::function & callback) const { - requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, false, true)); + requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, false, true, true)); } void CIdentifierStorage::requestIdentifierIfFound(const std::string & scope, const std::string & type, const std::string & name, const std::function & callback) const { - requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, false, true)); + requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, false, true, true)); } void CIdentifierStorage::tryRequestIdentifier(const std::string & scope, const std::string & type, const std::string & name, const std::function & callback) const { - requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, true, false)); + requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, true, false, true)); } void CIdentifierStorage::tryRequestIdentifier(const std::string & type, const JsonNode & name, const std::function & callback) const { - requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, true, false)); + requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, true, false, true)); } -std::optional CIdentifierStorage::getIdentifier(const std::string & scope, const std::string & type, const std::string & name, bool silent, bool caseSensitive) const +std::optional CIdentifierStorage::getIdentifier(const std::string & scope, const std::string & type, const std::string & name, bool silent) const { //assert(state != ELoadingState::LOADING); - auto options = ObjectCallback::fromNameAndType(scope, type, name, std::function(), silent, false, caseSensitive); + auto options = ObjectCallback::fromNameAndType(scope, type, name, std::function(), silent, false, true); return getIdentifierImpl(options, silent); } -std::optional CIdentifierStorage::getIdentifier(const std::string & type, const JsonNode & name, bool silent, bool caseSensitive) const +std::optional CIdentifierStorage::getIdentifier(const std::string & type, const JsonNode & name, bool silent) const { assert(state != ELoadingState::LOADING); - auto options = ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), std::function(), silent, false, caseSensitive); + auto options = ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), std::function(), silent, false, true); return getIdentifierImpl(options, silent); } -std::optional CIdentifierStorage::getIdentifier(const JsonNode & name, bool silent, bool caseSensitive) const +std::optional CIdentifierStorage::getIdentifier(const JsonNode & name, bool silent) const { assert(state != ELoadingState::LOADING); - auto options = ObjectCallback::fromNameWithType(name.getModScope(), name.String(), std::function(), silent, caseSensitive); + auto options = ObjectCallback::fromNameWithType(name.getModScope(), name.String(), std::function(), silent, true); return getIdentifierImpl(options, silent); } -std::optional CIdentifierStorage::getIdentifier(const std::string & scope, const std::string & fullName, bool silent, bool caseSensitive) const +std::optional CIdentifierStorage::getIdentifier(const std::string & scope, const std::string & fullName, bool silent) const { assert(state != ELoadingState::LOADING); - auto options = ObjectCallback::fromNameWithType(scope, fullName, std::function(), silent, caseSensitive); + auto options = ObjectCallback::fromNameWithType(scope, fullName, std::function(), silent, true); + return getIdentifierImpl(options, silent); +} + +std::optional CIdentifierStorage::getIdentifierCaseInsensitive(const std::string & scope, const std::string & type, const std::string & name, bool silent) const +{ + auto options = ObjectCallback::fromNameAndType(scope, type, name, std::function(), silent, false, false); return getIdentifierImpl(options, silent); } @@ -338,6 +344,7 @@ void CIdentifierStorage::registerObject(const std::string & scope, const std::st { logMod->trace("registered '%s' as %s:%s", fullID, scope, identifier); registeredObjects.insert(mapping); + registeredObjectsCaseLookup[boost::algorithm::to_lower_copy(mapping.first)] = mapping.first; } else { @@ -345,34 +352,6 @@ void CIdentifierStorage::registerObject(const std::string & scope, const std::st } } -template -std::pair -caseInsensitiveEqualRange(const MultiMap& mmap, const std::string& key) -{ - using ConstIt = typename MultiMap::const_iterator; - - std::string loweredKey = boost::algorithm::to_lower_copy(key); - - ConstIt first = mmap.end(); - ConstIt last = mmap.end(); - - for (ConstIt it = mmap.begin(); it != mmap.end(); ++it) - { - std::string loweredItKey = boost::algorithm::to_lower_copy(it->first); - - if (loweredItKey == loweredKey) - { - if (first == mmap.end()) - first = it; - last = std::next(it); - } else if (first != mmap.end()) - // We've already found the matching range and now it's over - break; - } - - return { first, last }; -} - std::vector CIdentifierStorage::getPossibleIdentifiers(const ObjectCallback & request) const { std::set allowedScopes; @@ -439,8 +418,9 @@ std::vector CIdentifierStorage::getPossibleIdent } std::string fullID = request.type + '.' + request.name; + std::string fullIDCaseCorrected = request.caseSensitive ? fullID : registeredObjectsCaseLookup.at(boost::algorithm::to_lower_copy(fullID)); - auto entries = request.caseSensitive ? registeredObjects.equal_range(fullID) : caseInsensitiveEqualRange(registeredObjects, fullID); + auto entries = registeredObjects.equal_range(fullIDCaseCorrected); if (entries.first != entries.second) { diff --git a/lib/modding/IdentifierStorage.h b/lib/modding/IdentifierStorage.h index efc89b8c4..826859cd0 100644 --- a/lib/modding/IdentifierStorage.h +++ b/lib/modding/IdentifierStorage.h @@ -37,10 +37,10 @@ class DLL_LINKAGE CIdentifierStorage bool caseSensitive; /// Builds callback from identifier in form "targetMod:type.name" - static ObjectCallback fromNameWithType(const std::string & scope, const std::string & fullName, const std::function & callback, bool optional, bool caseSensitive = true); + static ObjectCallback fromNameWithType(const std::string & scope, const std::string & fullName, const std::function & callback, bool optional, bool caseSensitive); /// Builds callback from identifier in form "targetMod:name" - static ObjectCallback fromNameAndType(const std::string & scope, const std::string & type, const std::string & fullName, const std::function & callback, bool optional, bool bypassDependenciesCheck, bool caseSensitive = true); + static ObjectCallback fromNameAndType(const std::string & scope, const std::string & type, const std::string & fullName, const std::function & callback, bool optional, bool bypassDependenciesCheck, bool caseSensitive); private: ObjectCallback() = default; @@ -58,6 +58,7 @@ class DLL_LINKAGE CIdentifierStorage }; std::multimap registeredObjects; + std::map registeredObjectsCaseLookup; mutable std::vector scheduledRequests; /// All non-optional requests that have failed to resolve, for debug & error reporting @@ -98,10 +99,11 @@ public: void tryRequestIdentifier(const std::string & type, const JsonNode & name, const std::function & callback) const; /// get identifier immediately. If identifier is not know and not silent call will result in error message - std::optional getIdentifier(const std::string & scope, const std::string & type, const std::string & name, bool silent = false, bool caseSensitive = true) const; - std::optional getIdentifier(const std::string & type, const JsonNode & name, bool silent = false, bool caseSensitive = true) const; - std::optional getIdentifier(const JsonNode & name, bool silent = false, bool caseSensitive = true) const; - std::optional getIdentifier(const std::string & scope, const std::string & fullName, bool silent = false, bool caseSensitive = true) const; + std::optional getIdentifier(const std::string & scope, const std::string & type, const std::string & name, bool silent = false) const; + std::optional getIdentifier(const std::string & type, const JsonNode & name, bool silent = false) const; + std::optional getIdentifier(const JsonNode & name, bool silent = false) const; + std::optional getIdentifier(const std::string & scope, const std::string & fullName, bool silent = false) const; + std::optional getIdentifierCaseInsensitive(const std::string & scope, const std::string & type, const std::string & name, bool silent) const; /// registers new object void registerObject(const std::string & scope, const std::string & type, const std::string & name, si32 identifier); diff --git a/server/processors/PlayerMessageProcessor.cpp b/server/processors/PlayerMessageProcessor.cpp index ab7b24def..5c7047cd1 100644 --- a/server/processors/PlayerMessageProcessor.cpp +++ b/server/processors/PlayerMessageProcessor.cpp @@ -428,7 +428,7 @@ void PlayerMessageProcessor::cheatGiveArmy(PlayerColor player, const CGHeroInsta { } - std::optional creatureId = LIBRARY->identifiers()->getIdentifier(ModScope::scopeGame(), "creature", creatureIdentifier, false, false); + std::optional creatureId = LIBRARY->identifiers()->getIdentifierCaseInsensitive(ModScope::scopeGame(), "creature", creatureIdentifier, false); if(creatureId.has_value()) { @@ -469,7 +469,7 @@ void PlayerMessageProcessor::cheatGiveArtifacts(PlayerColor player, const CGHero { for (auto const & word : words) { - auto artID = LIBRARY->identifiers()->getIdentifier(ModScope::scopeGame(), "artifact", word, false, false); + auto artID = LIBRARY->identifiers()->getIdentifierCaseInsensitive(ModScope::scopeGame(), "artifact", word, false); if(artID && LIBRARY->arth->objects[*artID]) gameHandler->giveHeroNewArtifact(hero, ArtifactID(*artID), ArtifactPosition::FIRST_AVAILABLE); } @@ -728,7 +728,7 @@ void PlayerMessageProcessor::cheatSkill(PlayerColor player, const CGHeroInstance return; } - std::optional skillId = LIBRARY->identifiers()->getIdentifier(ModScope::scopeGame(), "skill", identifier, false, false); + std::optional skillId = LIBRARY->identifiers()->getIdentifierCaseInsensitive(ModScope::scopeGame(), "skill", identifier, false); if(!skillId.has_value()) return;