//#define COMMAND_REGISTRY_EXTRA #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern Logger logger; #define ForEachParameterType(func) \ func(Bool); \ func(Int); \ func(Float); \ func(String); \ func(Actor); \ func(Player); \ func(BlockPos); \ func(Vec3); \ func(RawText); \ func(Message); \ func(JsonValue); \ func(Item); \ func(Block); \ func(Effect); \ func(Enum); \ func(SoftEnum); \ func(ActorType); \ func(Command); \ func(WildcardSelector); #define CatchDynamicCommandError(func, handle) \ catch (const seh_exception& e) { \ OutputError("Uncaught SEH Exception Detected!", e.code(), TextEncoding::toUTF8(e.what()), func, handle); \ } \ catch (const std::exception& e) { \ OutputError("Uncaught C++ Exception Detected!", errno, TextEncoding::toUTF8(e.what()), func, handle); \ } \ catch (...) { \ OutputError("Uncaught Exception Detected!", -1, "", func, handle); \ } // global variable and function namespace { bool serverCommandsRegistered = false; std::unordered_map> dynamicCommandInstances; std::vector> delaySetupCommandInstances; SRWLock delaySetupLock; using Result = DynamicCommand::Result; using ParameterType = DynamicCommand::ParameterType; using ParameterPtr = DynamicCommand::ParameterPtr; using ParameterData = DynamicCommand::ParameterData; using ParameterIndex = DynamicCommandInstance::ParameterIndex; namespace ParameterDataType { typedef bool Bool; typedef int Int; typedef float Float; typedef std::string String; typedef WildcardCommandSelector WildcardSelector; typedef CommandSelector Actor; typedef CommandSelector Player; typedef CommandPosition BlockPos; typedef CommandPositionFloat Vec3; typedef CommandRawText RawText; typedef CommandMessage Message; typedef Json::Value JsonValue; typedef CommandItem Item; typedef Block const* Block; typedef MobEffect const* Effect; // typedef CommandPosition Position; #ifdef USE_PARSE_ENUM_STRING typedef std::pair Enum; #else typedef int Enum; #endif // USE_PARSE_ENUM_STRING typedef std::string SoftEnum; typedef ActorDefinitionIdentifier const* ActorType; typedef std::unique_ptr Command; #ifdef ENABLE_PARAMETER_TYPE_POSTFIX typedef int Postfix; #endif // ENABLE_PARAMETER_TYPE_POSTFIX } // namespace ParameterDataType auto const ParameterSizeMap = std::unordered_map{ {ParameterType::Bool, std::max((size_t)8, sizeof(ParameterDataType::Bool))}, {ParameterType::Int, std::max((size_t)8, sizeof(ParameterDataType::Int))}, {ParameterType::Float, std::max((size_t)8, sizeof(ParameterDataType::Float))}, {ParameterType::Actor, std::max((size_t)8, sizeof(ParameterDataType::Actor))}, {ParameterType::Player, std::max((size_t)8, sizeof(ParameterDataType::Player))}, {ParameterType::String, std::max((size_t)8, sizeof(ParameterDataType::String))}, {ParameterType::BlockPos, std::max((size_t)8, sizeof(ParameterDataType::BlockPos))}, {ParameterType::Vec3, std::max((size_t)8, sizeof(ParameterDataType::Vec3))}, {ParameterType::RawText, std::max((size_t)8, sizeof(ParameterDataType::RawText))}, {ParameterType::Message, std::max((size_t)8, sizeof(ParameterDataType::Message))}, {ParameterType::JsonValue, std::max((size_t)8, sizeof(ParameterDataType::JsonValue))}, {ParameterType::Item, std::max((size_t)8, sizeof(ParameterDataType::Item))}, {ParameterType::Block, std::max((size_t)8, sizeof(ParameterDataType::Block))}, {ParameterType::Effect, std::max((size_t)8, sizeof(ParameterDataType::Effect))}, {ParameterType::Enum, std::max((size_t)8, sizeof(ParameterDataType::Enum))}, {ParameterType::SoftEnum, std::max((size_t)8, sizeof(ParameterDataType::SoftEnum))}, {ParameterType::ActorType, std::max((size_t)8, sizeof(ParameterDataType::ActorType))}, {ParameterType::Command, std::max((size_t)8, sizeof(ParameterDataType::Command))}, #ifdef ENABLE_PARAMETER_TYPE_POSTFIX {ParameterType::Postfix, std::max((size_t)8, sizeof(ParameterDataType::Postfix))}, #endif // ENABLE_PARAMETER_TYPE_POSTFIX }; inline void OutputError(std::string errorMsg, int errorCode, std::string errorWhat, std::string func, HMODULE handle) { logger.error(errorMsg); logger.error("Error: Code [{}] {}", errorCode, errorWhat); logger.error("In Function ({})", func); if (auto plugin = LL::getPlugin(handle)) logger.error("In Plugin <{}>", plugin->name); } } // namespace #pragma region Command init and destroy template inline void destruct(void* command, size_t offset) { dAccess(command, offset).~T(); } template inline void initValue(void* command, size_t offset) { dAccess(command, offset) = T(); } template <> inline void initValue(void* command, size_t offset) { dAccess(command, offset).basic_string::basic_string(); } template <> inline void initValue(void* command, size_t offset) { dAccess(command, offset).CommandItem::CommandItem(); } template <> inline void initValue(void* command, size_t offset) { dAccess(command, offset).CommandMessage::CommandMessage(); } template <> inline void initValue>(void* command, size_t offset) { dAccess>(command, offset).CommandSelector::CommandSelector(); } template <> inline void initValue>(void* command, size_t offset) { dAccess>(command, offset).CommandSelector::CommandSelector(); } template <> inline void initValue>(void* command, size_t offset) { dAccess>(command, offset).WildcardCommandSelector::WildcardCommandSelector(); } #pragma endregion #pragma region ParameterPtr inline DynamicCommand::ParameterPtr::ParameterPtr(ParameterType type, size_t offset) : type(type) , offset(offset) { } inline bool DynamicCommand::ParameterPtr::isValueSet(DynamicCommand const* command) const { return dAccess(command, offset + ParameterSizeMap.at(type)); } Result ParameterPtr::getResult(DynamicCommand const* command, CommandOrigin const* origin) const { // auto commandInstance = dynamicCommandInstances.at(command->getCommandName()).get(); return {this, command, origin}; } #pragma endregion #pragma region ParameterData DynamicCommand::ParameterData::ParameterData(ParameterData const& right) : ParameterData(right.name, right.type, right.optional, right.description, right.identifier, right.option) { offset = right.offset; }; inline DynamicCommand::ParameterData::ParameterData(std::string const& name, ParameterType type, bool optional, std::string const& enumOptions, std::string const& identifier, CommandParameterOption parameterOption) : name(name) , type(type) , optional(optional) , description(enumOptions) , option(parameterOption) { if (identifier.empty()) this->identifier = description.empty() ? name : description; else this->identifier = identifier; if (type != DynamicCommand::ParameterType::Enum && type != DynamicCommand::ParameterType::SoftEnum) { if (!description.empty()) description = ""; } else { if (description.empty()) throw std::runtime_error("Enum or SoftEnum parameter need a description to confirm which enum to use"); } } inline DynamicCommand::ParameterData::ParameterData(std::string const& name, DynamicCommand::ParameterType type, std::string const& enumOptions, std::string const& identifier, CommandParameterOption parameterOption) : ParameterData(name, type, false, enumOptions, identifier, parameterOption) { } inline CommandParameterData DynamicCommand::ParameterData::makeParameterData() const { switch (type) { case ParameterType::Bool: return makeParameterData(); case ParameterType::Int: return makeParameterData(); case ParameterType::Float: return makeParameterData(); case ParameterType::String: return makeParameterData(); case ParameterType::Actor: return makeParameterData(); case ParameterType::Player: return makeParameterData(); case ParameterType::BlockPos: return makeParameterData(); case ParameterType::Vec3: return makeParameterData(); case ParameterType::RawText: return makeParameterData(); case ParameterType::Message: return makeParameterData(); case ParameterType::JsonValue: return makeParameterData(); case ParameterType::Item: return makeParameterData(); case ParameterType::Block: return makeParameterData(); case ParameterType::Effect: return makeParameterData(); // case ParameterType::Position: // return makeParameterData(); case ParameterType::Enum: return makeParameterData(); case ParameterType::SoftEnum: return makeParameterData(); case ParameterType::ActorType: return makeParameterData(); case ParameterType::Command: return makeParameterData(); #ifdef ENABLE_PARAMETER_TYPE_POSTFIX case ParameterType::Postfix: return makeParameterData(); #endif // ENABLE_PARAMETER_TYPE_POSTFIX default: return {}; } } #pragma endregion #pragma region Result inline DynamicCommand::Result::Result(ParameterPtr const* ptr, DynamicCommand const* command, CommandOrigin const* origin, DynamicCommandInstance const* instance) : type(ptr->type) , offset(ptr->offset) , command(command) , origin(origin) , instance(instance ? instance : command->getInstance()) , isSet(ptr->isValueSet(command)) { } inline DynamicCommand::Result::Result() : type((ParameterType)-1) , offset(-1) , command(nullptr) , origin(nullptr) , instance(nullptr) , isSet(false) { } inline std::string const& DynamicCommand::Result::getEnumValue() const { if (getType() == ParameterType::Enum) { return getRaw(); } else if (getType() == ParameterType::SoftEnum) { return getRaw(); } static std::string const EMPTY_STRING = ""; return EMPTY_STRING; } inline ParameterType DynamicCommand::Result::getType() const { return type; } inline std::string DynamicCommand::Result::getName() const { for (auto& [name, ptr] : instance->parameterPtrs) { if (ptr.getOffset() == offset) return name; } return ""; } std::string DynamicCommand::Result::toDebugString() const { std::string name = getName(); ParameterType type = getType(); std::string typeName = fmt::format("{}({})", magic_enum::enum_name(type), (int)type); switch (type) { case ParameterType::Bool: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw()); case ParameterType::Int: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw()); case ParameterType::Float: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw()); case ParameterType::Actor: // return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw>().getName()); case ParameterType::Player: { std::vector actors = get>(); std::ostringstream oss; oss << "count: " << actors.size() << ", actors: ["; bool first = true; for (auto& actor : actors) { if (!first) oss << ", "; oss << CommandUtils::getActorName(*actor); } oss << "]"; return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, oss.str()); } case ParameterType::String: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw()); case ParameterType::BlockPos: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw().serialize().toSNBT()); case ParameterType::Vec3: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw().serialize().toSNBT()); case ParameterType::RawText: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw().getText()); case ParameterType::Message: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw().getMessage(*origin)); case ParameterType::JsonValue: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw().toStyledString().substr(0, getRaw().toStyledString().size() - 1)); case ParameterType::Item: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw().createInstance(1, 1, nullptr, true).value_or(ItemInstance::EMPTY_ITEM).toString()); case ParameterType::Block: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, isSet ? getRaw()->toDebugString() : "nullptr"); case ParameterType::Effect: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, isSet ? getRaw()->getResourceName() : "nullptr"); case ParameterType::Enum: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, fmt::format("{}({})", getRaw(), getRaw())); case ParameterType::SoftEnum: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw()); case ParameterType::ActorType: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, isSet ? getRaw()->getCanonicalName() : "Null"); case ParameterType::Command: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, isSet ? getRaw>()->getCommandName() : "Null"); #ifdef ENABLE_PARAMETER_TYPE_POSTFIX case ParameterType::Postfix: return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, get()); #endif // ENABLE_PARAMETER_TYPE_POSTFIX default: logger.error("Unknown Parameter Type {}, name: {}", typeName, name); return ""; } } inline DynamicCommandInstance const* DynamicCommand::Result::getInstance() const { std::string commandName = command->getCommandName(); auto iter = dynamicCommandInstances.find(commandName); if (iter == dynamicCommandInstances.end()) return nullptr; return iter->second.get(); } #pragma endregion #pragma region DynamicCommand // std::unique_ptr DynamicCommand::commandBuilder() //{ // std::unique_ptr command; // commandBuilder2(&command, latestAllocateName); // return std::move(command); // } inline char DynamicCommand::builderCallbackHanler(DCCallback* cb, DCArgs* args, DCValue* result, void* userdata) { DynamicCommandInstance& command = *(DynamicCommandInstance*)userdata; auto arg1 = (std::unique_ptr*)dcbArgPointer(args); arg1->release(); result->p = DynamicCommand::commandBuilder(arg1, command.getCommandName()); return 'p'; } std::unique_ptr* DynamicCommand::commandBuilder(std::unique_ptr* rtn, std::string name) { #define CaseInitBreak(type) \ case ParameterType::type: \ initValue(command, offset); \ break; assert(dynamicCommandInstances.count(name) == 1); if (dynamicCommandInstances.count(name) == 0) { logger.error("Error in allocate dynamic command"); return rtn; } auto& commandInstance = *dynamicCommandInstances.at(name); auto command = new char[commandInstance.commandSize]{0}; (*(DynamicCommand*)command).DynamicCommand::DynamicCommand(); for (auto& [name, param] : commandInstance.parameterPtrs) { size_t offset = param.getOffset(); dAccess(command, offset + ParameterSizeMap.at(param.type)) = false; // XXXX_isSet; switch (param.type) { ForEachParameterType(CaseInitBreak); default: break; } } rtn->reset(std::move((Command*)command)); return rtn; } DynamicCommandInstance* DynamicCommand::_setup(std::unique_ptr commandInstance) { std::string name = commandInstance->getCommandName(); #ifdef DEBUG logger.info("Setting up command \"{}\"", name); #endif // DEBUG // Check if there is another command with the same name auto signature = Global->findCommand(name); if (signature) { throw std::runtime_error("There is already a command named " + signature->name); } auto handle = commandInstance->handle; try { if (!commandInstance) throw std::runtime_error("Command instance is null"); if (!commandInstance->callback) throw std::runtime_error("Can't setup command without callback"); if (commandInstance->overloads.empty()) throw std::runtime_error("Can't setup command without overloads"); // commandInstance->updateSoftEnum(); for (auto& param : commandInstance->parameterDatas) { if (param.type == ParameterType::Enum) { // clone BDS's enum if (commandInstance->enumRanges.count(param.description) == 0) { auto namesInBds = CommandRegistry::getEnumNames(); auto iter = std::find(namesInBds.begin(), namesInBds.end(), param.description); if (iter == namesInBds.end()) throw std::runtime_error("Enum " + std::string(param.description) + "not found in command and BDS"); #ifndef USE_PARSE_ENUM_STRING_ // fix Enum commandInstance->setEnum(*iter, CommandRegistry::getEnumValues(*iter)); #endif // USE_PARSE_ENUM_STRING } } else if (param.type == ParameterType::SoftEnum) { // add empty Soft Enum if not found in command and BDS if (commandInstance->softEnums.count(param.description) == 0) { auto namesInBds = CommandRegistry::getSoftEnumNames(); auto iter = std::find(namesInBds.begin(), namesInBds.end(), param.description); if (iter == namesInBds.end()) commandInstance->setSoftEnum(param.description, {}); } } } // fix enum name with prefix '_...' if Enum name is exists in BDS auto namesInBds = CommandRegistry::getEnumNames(); std::unordered_map> convertedEnumRanges; for (auto& [name, range] : commandInstance->enumRanges) { std::string fixedName = name.data(); while (std::find(namesInBds.begin(), namesInBds.end(), fixedName) != namesInBds.end()) { fixedName.append("_"); } std::string_view fixedView = name; if (fixedName != name) { for (auto& namePtr : commandInstance->enumNames) { if (*namePtr == name) { namePtr->swap(fixedName); fixedView = *namePtr; for (auto& data : commandInstance->parameterDatas) { if (data.description == fixedName) { data.description = *namePtr; } } break; } } } std::vector> values; size_t index = range.first; for (auto iter = commandInstance->enumValues.begin() + range.first; iter != commandInstance->enumValues.begin() + range.first + range.second; ++iter) { values.emplace_back(*iter, index); ++index; } #ifdef USE_PARSE_ENUM_STRING Global->addEnumValuesInternal(fixedView.data(), values, typeid_t::count++, &CommandRegistry::parseEnumStringAndInt).val; #else Global->addEnumValuesInternal(fixedView.data(), values, typeid_t::count++, &CommandRegistry::parseEnum).val; #endif // USE_PARSE_ENUM_STRING } commandInstance->enumRanges.swap(convertedEnumRanges); // add Soft Enum to BDS for (auto& [name, values] : commandInstance->softEnums) { Global->addSoftEnum(name, values); } Global->registerCommand(commandInstance->name, commandInstance->description->c_str(), commandInstance->permission, commandInstance->flag, commandInstance->flag); if (!commandInstance->alias.empty()) Global->registerAlias(commandInstance->name, commandInstance->alias); auto builder = commandInstance->initCommandBuilder(); for (auto& overload : commandInstance->overloads) { Global->registerOverload(commandInstance->name, builder, commandInstance->buildOverload(overload)); } // commandInstance->overloads.clear(); auto res = dynamicCommandInstances.emplace(commandInstance->name, std::move(commandInstance)); return res.first->second.get(); } CatchDynamicCommandError("DynamicCommand::_setup - " + name, handle); return nullptr; } bool DynamicCommand::onServerCommandsRegister(CommandRegistry& registry) { serverCommandsRegistered = true; SRWLockHolder locker(delaySetupLock); for (auto& command : delaySetupCommandInstances) { std::string name = command->getCommandName(); auto handle = command->handle; try { if (!LL::getPlugin(handle) && handle != GetCurrentModule()) throw std::runtime_error("Plugin that registered command \"" + name + "\" not found"); auto res = DynamicCommand::_setup(std::move(command)); if (!res) throw std::runtime_error("Command \"" + name + "\" setup failed"); } CatchDynamicCommandError("DynamicCommand::_setup - " + name, handle); }; delaySetupCommandInstances.clear(); return true; } DynamicCommand::~DynamicCommand() { #define CaseDestructBreak(type) \ case ParameterType::type: \ destruct(this, offset); \ break; std::string commandName = getCommandName(); auto iter = dynamicCommandInstances.find(commandName); if (iter == dynamicCommandInstances.end()) { logger.error("Error in DynamicCommand::~DynamicCommand(), command \"{}\" not found", commandName); return; } auto& commandIns = *iter->second; for (auto& [name, parameter] : commandIns.parameterPtrs) { auto offset = parameter.getOffset(); switch (parameter.type) { ForEachParameterType(CaseDestructBreak); default: break; } } } void DynamicCommand::execute(CommandOrigin const& origin, CommandOutput& output) const { auto iter = dynamicCommandInstances.find(getCommandName()); if (iter == dynamicCommandInstances.end()) { return output.error("Dynamic Command Not Found"); } auto& commandIns = *iter->second; if (!commandIns.callback) { return output.error(fmt::format("Command {} has been removed.", getCommandName())); } try { std::unordered_map results; for (auto& [name, param] : commandIns.parameterPtrs) { results.emplace(name, param.getResult(this, &origin)); } commandIns.callback(*this, origin, output, results); } CatchDynamicCommandError("DynamicCommand::execute", commandIns.handle); } std::unique_ptr DynamicCommand::createCommand(std::string const& name, std::string const& description, CommandPermissionLevel permission, CommandFlag flag1, CommandFlag flag2, HMODULE handle) { return DynamicCommandInstance::create(name, description, permission, flag1 |= flag2, handle); } #include #include #include
DynamicCommandInstance const* DynamicCommand::setup(std::unique_ptr commandInstance) { auto ptr = commandInstance.get(); if (!ptr) throw std::runtime_error("DynamicCommand::setup - commandInstance is null"); if (!serverCommandsRegistered) { delaySetupLock.lock(); auto& uptr = delaySetupCommandInstances.emplace_back(std::move(commandInstance)); delaySetupLock.unlock(); return uptr.get(); } // logger.warn("Registering command \"{}\" after RegCmdEvent, note that this is unstable!", commandInstance->getCommandName()); Schedule::nextTick([instance{commandInstance.release()}]() { if (!_setup(std::unique_ptr(instance))) logger.warn("Registering command \"{}\" failed", instance->getCommandName()); updateAvailableCommands(); }); return ptr; } std::unique_ptr DynamicCommand::createCommand(std::string const& name, std::string const& description, std::unordered_map>&& enums, std::vector&& params, std::vector>&& overloads, CallBackFn callback, CommandPermissionLevel permission, CommandFlag flag1, CommandFlag flag2, HMODULE handle) { auto command = createCommand(name, description, permission, flag1, flag2, handle); if (!command) return std::unique_ptr(); for (auto& [name, values] : enums) { command->setEnum(name, std::move(values)); } for (auto& param : params) { command->newParameter(std::move(param)); } for (auto& overload : overloads) { command->addOverload(std::move(overload)); } command->setCallback(std::move(callback)); return std::move(command); } bool DynamicCommand::unregisterCommand(std::string const& name) { #ifdef DEBUG Schedule::nextTick([tid = std::this_thread::get_id()]() { // Call DynamicCommand::unregisterCommand in other thread is not allowed! assert(tid == std::this_thread::get_id()); }); #endif // DEBUG if (Global->unregisterCommand(name)) { dynamicCommandInstances.erase(name); updateAvailableCommands(); return true; } return false; } inline bool DynamicCommand::updateAvailableCommands() { #ifdef DEBUG Schedule::nextTick([tid = std::this_thread::get_id()]() { // Call DynamicCommand::updateAvailableCommands in other thread is not allowed! assert(tid == std::this_thread::get_id()); }); #endif // DEBUG if (!Global || !Global) return false; auto packet = Global->serializeAvailableCommands(); auto sender = (LoopbackPacketSender*)Global->getPacketSender(); if (!sender) return false; sender->sendBroadcast(packet); return true; } inline DynamicCommandInstance const* DynamicCommand::getInstance() const { return getInstance(getCommandName()); } DynamicCommandInstance const* DynamicCommand::getInstance(std::string const& commandName) { auto iter = dynamicCommandInstances.find(commandName); if (iter == dynamicCommandInstances.end()) return nullptr; else return iter->second.get(); } #pragma endregion #pragma region DynamicCommandInstance inline DynamicCommandInstance::DynamicCommandInstance(std::string const& name, std::string const& description, CommandPermissionLevel permission, CommandFlag flag, HMODULE handle) : name(name) , description(std::make_unique(description)) , permission(permission) , flag(flag) , handle(handle){}; inline DynamicCommandInstance::~DynamicCommandInstance() { if (this->builder) dcbFreeCallback((DCCallback*)this->builder); this->builder = nullptr; } inline std::unique_ptr DynamicCommandInstance::create(std::string const& name, std::string const& description, CommandPermissionLevel permission, CommandFlag flag, HMODULE handle) { if (LL::globalConfig.serverStatus != LL::LLServerStatus::Running) { for (auto& cmd : delaySetupCommandInstances) { if (cmd->name == name) { logger.error("Command \"{}\" already exists", name); return {}; } } } else if (Global->findCommand(name)) { logger.error("Command \"{}\" already exists", name); return {}; } return std::unique_ptr(new DynamicCommandInstance(name, description, permission, flag, handle)); } inline bool DynamicCommandInstance::addOverload(std::vector&& params) { std::vector indices; for (auto& param : params) { indices.push_back(newParameter(std::forward(param))); } return addOverload(std::move(indices)); } inline std::string const& DynamicCommandInstance::setEnum(std::string const& description, std::vector const& values) { auto& desc = enumNames.emplace_back(std::make_unique(description)); enumRanges.emplace(*desc, std::pair{enumValues.size(), values.size()}); enumValues.insert(enumValues.end(), values.begin(), values.end()); return *desc; } inline std::string const& DynamicCommandInstance::getEnumValue(int index) const { if (index < 0 || index >= enumValues.size()) throw std::runtime_error("Enum index out of range"); return enumValues.at(index); } ParameterIndex DynamicCommandInstance::newParameter(DynamicCommand::ParameterData&& data) { auto iter = parameterPtrs.find(data.name); size_t offset = -1; if (iter == parameterPtrs.end()) { offset = commandSize; parameterPtrs.emplace(data.name, DynamicCommand::ParameterPtr(data.type, offset)); commandSize += ParameterSizeMap.at(data.type) + 8; } else { offset = iter->second.getOffset(); if (iter->second.type != data.type) throw std::runtime_error(fmt::format("dynamic command \"{}\" register failed, Different type parameters with the same name {} are not allowed", name, data.name)); } std::string const& identifier = data.identifier; if (parameterDatas.end() != std::find_if(parameterDatas.begin(), parameterDatas.end(), [&](DynamicCommand::ParameterData const& data) { return data.identifier == identifier; })) throw std::runtime_error("parameter identifier already exists"); data.offset = offset; parameterDatas.emplace_back(std::move(data)); return {this, parameterDatas.size() - 1}; } inline ParameterIndex DynamicCommandInstance::newParameter(std::string const& name, DynamicCommand::ParameterType type, bool optional, std::string const& description, std::string const& identifier, CommandParameterOption parameterOption) { return newParameter(ParameterData(name, type, optional, description, identifier, parameterOption)); } inline ParameterIndex DynamicCommandInstance::findParameterIndex(std::string const& indentifier) { size_t index = 0; for (auto& paramData : parameterDatas) { if (paramData.identifier == indentifier || paramData.description == indentifier || paramData.name == indentifier) break; ++index; } if (index == parameterDatas.size()) index = -1; return {this, index}; } inline ParameterIndex DynamicCommandInstance::mandatory(std::string const& name, DynamicCommand::ParameterType type, std::string const& description, std::string const& identifier, CommandParameterOption parameterOption) { return newParameter(ParameterData(name, type, false, description, identifier, parameterOption)); } inline ParameterIndex DynamicCommandInstance::mandatory(std::string const& name, DynamicCommand::ParameterType type, std::string const& description, CommandParameterOption parameterOption) { return mandatory(name, type, description, "", parameterOption); }; inline ParameterIndex DynamicCommandInstance::mandatory(std::string const& name, DynamicCommand::ParameterType type, CommandParameterOption parameterOption) { return mandatory(name, type, "", "", parameterOption); }; inline ParameterIndex DynamicCommandInstance::optional(std::string const& name, DynamicCommand::ParameterType type, std::string const& description, std::string const& identifier, CommandParameterOption parameterOption) { return newParameter(ParameterData(name, type, true, description, identifier, parameterOption)); } inline ParameterIndex DynamicCommandInstance::optional(std::string const& name, DynamicCommand::ParameterType type, std::string const& description, CommandParameterOption parameterOption) { return optional(name, type, description, "", parameterOption); } inline ParameterIndex DynamicCommandInstance::optional(std::string const& name, DynamicCommand::ParameterType type, CommandParameterOption parameterOption) { return optional(name, type, "", "", parameterOption); } bool DynamicCommandInstance::addOverload(std::vector&& params) { for (auto& index : params) { if (index >= parameterDatas.size()) throw std::runtime_error("parameter index " + std::to_string(index) + " out of range 0 ~ " + std::to_string(parameterDatas.size())); } overloads.emplace_back(params); return true; } inline bool DynamicCommandInstance::addOverload(std::vector&& params) { std::vector paramIndices; for (auto& param : params) { auto index = findParameterIndex(param); if (!index.isValid()) { throw std::runtime_error("Parameter " + std::string(param) + "not found"); } paramIndices.push_back(index); } return addOverload(std::move(paramIndices)); } inline bool DynamicCommandInstance::addOverload(std::vector&& params) { std::vector paramIndices; for (auto& param : params) { paramIndices.push_back(findParameterIndex(param)); } return addOverload(std::move(paramIndices)); } inline bool DynamicCommandInstance::setAlias(std::string const& alias) { this->alias = alias; return true; } inline std::vector DynamicCommandInstance::buildOverload(std::vector const& overload) { std::vector datas; for (auto& index : overload) { auto& param = parameterDatas.at(index); datas.emplace_back(param.makeParameterData()); } return datas; } // bool DynamicCommandInstance::updateSoftEnum(std::string const& name) const //{ // if (!hasRegistered()) // { // for (auto& [key, values] : softEnumValues) // { // Global->addSoftEnum(key, values); // } // return true; // } // if (!name.empty()) // { // auto iter = softEnumValues.find(name); // if (iter != softEnumValues.end()) // CommandSoftEnumRegistry(Global).updateSoftEnum(SoftEnumUpdateType::Set, iter->first, iter->second); // else // return false; // return true; // } // else // { // for (auto& [key, values] : softEnumValues) // { // CommandSoftEnumRegistry(Global).updateSoftEnum(SoftEnumUpdateType::Set, key, values); // } // } // } std::string DynamicCommandInstance::setSoftEnum(std::string const& name, std::vector const& values) const { if (!hasRegistered()) { softEnums.emplace(name, values); } else { if (!Global) return ""; auto names = CommandRegistry::getSoftEnumNames(); if (std::find(names.begin(), names.end(), name) == names.end()) { Global->addSoftEnum(name, values); return name; } CommandSoftEnumRegistry(Global).updateSoftEnum(SoftEnumUpdateType::Set, name, values); } return name; } bool DynamicCommandInstance::addSoftEnumValues(std::string const& name, std::vector const& values) const { if (!hasRegistered()) { auto iter = softEnums.find(name); if (iter != softEnums.end()) { iter->second.insert(iter->second.end(), values.begin(), values.end()); } else { setSoftEnum(name, values); } } else { if (!Global) return false; auto names = CommandRegistry::getSoftEnumNames(); if (std::find(names.begin(), names.end(), name) == names.end()) { Global->addSoftEnum(name, values); return true; } CommandSoftEnumRegistry(Global).updateSoftEnum(SoftEnumUpdateType::Add, name, values); } return true; }; bool DynamicCommandInstance::removeSoftEnumValues(std::string const& name, std::vector const& values) const { if (!hasRegistered()) { auto iter = softEnums.find(name); if (iter != softEnums.end()) { auto tmp = std::remove_if( iter->second.begin(), iter->second.end(), [values](std::string const& val) { return std::find(values.begin(), values.end(), val) != values.end(); }); iter->second.erase(tmp, iter->second.end()); return true; } return false; } else { if (Global) CommandSoftEnumRegistry(Global).updateSoftEnum(SoftEnumUpdateType::Remove, name, values); } return true; } inline std::vector DynamicCommandInstance::getSoftEnumValues(std::string const& name) { return CommandRegistry::getSoftEnumValues(name); } inline std::vector DynamicCommandInstance::getSoftEnumNames() { return CommandRegistry::getSoftEnumNames(); } inline void DynamicCommandInstance::setCallback(DynamicCommand::CallBackFn&& callback) const { this->callback = callback; } inline void DynamicCommandInstance::removeCallback() const { callback = nullptr; } inline std::string const& DynamicCommandInstance::getCommandName() const { return name; } inline bool DynamicCommandInstance::setBuilder(DynamicCommand::BuilderFn builder) { if (this->builder == nullptr) this->builder = builder; else return false; return true; } inline DynamicCommand::BuilderFn DynamicCommandInstance::initCommandBuilder() { auto builder = (DynamicCommand::BuilderFn)dcbNewCallback("ifsdl)p", &DynamicCommand::builderCallbackHanler, this); if (this->setBuilder(builder)) return builder; dcbFreeCallback((DCCallback*)builder); return nullptr; } #pragma endregion #ifdef DEBUG #define TEST_DYNAMIC_COMMAND #define successf(...) success(fmt::format(__VA_ARGS__)) #define errorf(...) error(fmt::format(__VA_ARGS__)) using Param = DynamicCommand::ParameterData; using ParamType = DynamicCommand::ParameterType; using ParamIndex = DynamicCommandInstance::ParameterIndex; #ifdef TEST_DYNAMIC_COMMAND #include #include #include void setupTestParamCommand() { using Param = DynamicCommand::ParameterData; using ParamType = DynamicCommand::ParameterType; Param boolParam("testBool", ParamType::Bool, true); Param intParam("testInt", ParamType::Int, true); Param floatParam("testFloat", ParamType::Float, true); Param strParam("testStr", ParamType::String, true); Param actorParam("testActor", ParamType::Actor, true); Param playerParam("testPlayer", ParamType::Player, true); Param BlockPosParam("testBlockPos", ParamType::BlockPos, true); Param Vec3Param("testVec3", ParamType::Vec3, true); Param RawTextParam("testRawText", ParamType::RawText, true); Param MessageParam("testMessage", ParamType::Message, true); Param JsonValueParam("testJsonValue", ParamType::JsonValue, true); Param ItemParam("testItem", ParamType::Item, true); Param BlockParam("testBlock", ParamType::Block, true); Param ActorTypeParam("testActorType", ParamType::ActorType, true); Param EffectParam("testEffect", ParamType::Effect, true); Param CommandParam("testCommand", ParamType::Command, true); // Param posParam(ParamType::Position, "testPos", true); // Test Command: dynparam true 114 3.14 str @e @a 3 1 4 ~3 ~1 ~4 raw text msg {"a":4} stick concrete speed version DynamicCommand::setup( "param", "dynamic command", {}, { boolParam, intParam, floatParam, strParam, actorParam, playerParam, BlockPosParam, Vec3Param, RawTextParam, MessageParam, JsonValueParam, ItemParam, BlockParam, ActorTypeParam, EffectParam, CommandParam, }, {{ "testActorType", "testBool", "testInt", "testFloat", "testStr", "testActor", "testPlayer", "testBlockPos", "testVec3", //"testRawText", //"testMessage", //"testJsonValue", //"testItem", //"testBlock", //"testEffect", //"testCommand", }}, [](DynamicCommand const& command, CommandOrigin const& origin, CommandOutput& output, std::unordered_map& results) { for (auto& [name, result] : results) { output.success(result.toDebugString()); } }, CommandPermissionLevel::Any); } void setupTestEnumCommand() { using ParamType = DynamicCommand::ParameterType; using Param = DynamicCommand::ParameterData; DynamicCommand::setup( "testenum", // command name "dynamic command", // command description { // enums{enumName, {values...}} {"TestEnum1", {"add", "remove"}}, {"TestEnum2", {"list"}}, }, { // parameters(type, name, [optional], [enumOptions(also enumName)], [identifier]) // identifier: used to identify unique parameter data, if idnetifier is not set, // it is set to be the same as enumOptions or name (identifier = enumOptions.empty() ? name:enumOptions) Param("testEnum", ParamType::Enum, "TestEnum1", "", CommandParameterOption::EnumAutocompleteExpansion), Param("testEnum", ParamType::Enum, "TestEnum2", "", CommandParameterOption::EnumAutocompleteExpansion), Param("testInt", ParamType::Int, true), }, { // overloads{ (type == Enum ? enumOptions : name) ...} {"TestEnum1", "testInt"}, // testenum [testInt] {"TestEnum2"}, // testenum }, // dynamic command callback [](DynamicCommand const& command, CommandOrigin const& origin, CommandOutput& output, std::unordered_map& results) { auto& action = results["testEnum"].getRaw(); switch (do_hash(action.c_str())) { case do_hash("add"): if (results["testInt"].isSet) output.success(fmt::format("add {}", results["testInt"].getRaw())); else output.success("add nothing"); break; case do_hash("remove"): if (results["testInt"].isSet) output.success(fmt::format("remove {}", results["testInt"].getRaw())); else output.success("remove nothing"); break; case do_hash("list"): output.success("list"); break; default: break; } }, CommandPermissionLevel::GameMasters); } void setupExampleCommand() { using ParamType = DynamicCommand::ParameterType; // create a dynamic command auto command = DynamicCommand::createCommand("testcmd", "dynamic command", CommandPermissionLevel::GameMasters); auto& optionsAdd = command->setEnum("TestOperation1", {"add", "remove"}); auto& optionsList = command->setEnum("TestOperation2", {"list"}); command->mandatory("testEnum", ParamType::Enum, optionsAdd, CommandParameterOption::EnumAutocompleteExpansion); command->mandatory("testEnum", ParamType::Enum, optionsList, CommandParameterOption::EnumAutocompleteExpansion); command->mandatory("testString", ParamType::String); command->addOverload({optionsAdd, "testString"}); // dyncmd command->addOverload({"TestOperation2"}); // dyncmd command->setCallback([](DynamicCommand const& command, CommandOrigin const& origin, CommandOutput& output, std::unordered_map& results) { switch (do_hash(results["testEnum"].getRaw().c_str())) { case do_hash("add"): output.success(fmt::format("Add - {}", results["testString"].getRaw())); break; case do_hash("remove"): output.success(fmt::format("Remove - {}", results["testString"].getRaw())); break; case do_hash("list"): output.success("List"); break; default: break; } }); // do not forget to setup the command instance DynamicCommand::setup(std::move(command)); } // "remove command" command void setupRemoveCommand() { auto command = DynamicCommand::createCommand("unregister", "unregister command", CommandPermissionLevel::Any); command->setAlias("remove"); auto name = command->mandatory("name", ParamType::SoftEnum, command->setSoftEnum("CommandNames", {})); command->addOverload(name); command->setCallback( [](DynamicCommand const& cmd, CommandOrigin const& origin, CommandOutput& output, std::unordered_map& results) { auto& name = results["name"].getRaw(); auto fullName = Global->getCommandFullName(name); if (fullName == cmd.getCommandName()) { output.success("Request unregister itself"); Schedule::delay([fullName]() { auto res = Global->unregisterCommand(fullName); if (res) { dynamicCommandInstances.erase(fullName); logger.info("unregister command " + fullName); ((DynamicCommandInstance*)0)->setSoftEnum("CommandNames", CommandRegistry::getEnumValues("CommandName")); } else logger.error("error in unregister command " + fullName); }, 20); return; } auto res = Global->unregisterCommand(fullName); if (res) { dynamicCommandInstances.erase(fullName); output.success("unregister command " + fullName); cmd.getInstance()->setSoftEnum("CommandNames", CommandRegistry::getEnumValues("CommandName")); } else output.error("error in unregister command " + fullName); }); command->setSoftEnum("CommandNames", CommandRegistry::getEnumValues("CommandName")); DynamicCommand::setup(std::move(command)); } // TInstanceHook(void, "?run@Command@@QEBAXAEBVCommandOrigin@@AEAVCommandOutput@@@Z", // Command, class CommandOrigin const& origin, class CommandOutput& output) //{ // logger.info("Command({})::run()", getCommandName()); // } // force enable cheat TClasslessInstanceHook(bool, "?hasCommandsEnabled@LevelData@@QEBA_NXZ") { return true; } TInstanceHook(CommandParameterData&, "?addOptions@CommandParameterData@@QEAAAEAV1@W4CommandParameterOption@@@Z", CommandParameterData, CommandParameterOption option) { // logger.warn("CommandParameterData::addOptions - name: {}, type: {}, desc: {}, option: {:x}", // name, magic_enum::enum_name(type), desc ? desc : "", (int)option); return original(this, option); } #endif // TEST_DYNAMIC_COMMAND // enum command void onEnumExecute(DynamicCommand const& cmd, CommandOrigin const& origin, CommandOutput& output, std::unordered_map& results) { auto enumNames = Global->getEnumNames(); auto softEnumNames = Global->getSoftEnumNames(); cmd.getInstance()->setSoftEnum("EnumNameList", enumNames); cmd.getInstance()->addSoftEnumValues("EnumNameList", softEnumNames); if (results["name"].isSet) { auto& enumName = results["name"].getRaw(); bool found = false; if (std::find(enumNames.begin(), enumNames.end(), enumName) != enumNames.end()) { found = true; output.successf("§eEnum §l{}§r§e Values:", enumName); for (auto& val : CommandRegistry::getEnumValues(enumName)) { output.success(val); // output.addToResultList("enums", val); } // output.success("Enums: %1$s", {CommandOutputParameter("enums")}); } if (std::find(softEnumNames.begin(), softEnumNames.end(), enumName) != softEnumNames.end()) { found = true; output.successf("§eSoft Enum §l{}§r§e Values:", enumName); for (auto& val : CommandRegistry::getSoftEnumValues(enumName)) { output.success(val); } } if (!found) output.errorf("Enum or Soft Enum \"{}\" not found", enumName); } else { output.success("§eEnum Names:"); for (auto& val : CommandRegistry::getEnumNames()) { output.success(val); } output.success("§eSoft Enum Names:"); for (auto& val : CommandRegistry::getSoftEnumNames()) { output.success(val); } } } void setupEnumCommand() { auto command = DynamicCommand::createCommand("enum", "get command enum names or values", CommandPermissionLevel::Any); command->setAlias("enums"); auto name = command->mandatory("name", ParamType::SoftEnum, command->setSoftEnum("EnumNameList", {})); command->addOverload(name); command->addOverload(); command->setCallback(onEnumExecute); auto cmd = DynamicCommand::setup(std::move(command)); Schedule::delay( [cmd]() { auto packet = Global->serializeAvailableCommands(); cmd->setSoftEnum("EnumNameList", packet.getEnumNames()); cmd->addSoftEnumValues("EnumNameList", packet.getSoftEnumNames()); }, 10); } // echo command void setupEchoCommand() { auto command = DynamicCommand::createCommand("echo", "show message", CommandPermissionLevel::Any); command->addOverload(command->mandatory("text", ParamType::RawText)); command->setCallback( [](DynamicCommand const& cmd, CommandOrigin const& origin, CommandOutput& output, std::unordered_map& results) { auto& text = results["text"].getRaw(); output.success(text); }); DynamicCommand::setup(std::move(command)); } TClasslessInstanceHook2("startServerThread_RegisterDebugCommand", void, "?startServerThread@ServerInstance@@QEAAXXZ") { #ifdef DEBUG setupRemoveCommand(); setupTestEnumCommand(); setupTestParamCommand(); setupExampleCommand(); #if false Global->printAll(); Schedule::delayRepeat( []() { static bool Switch = true; if (Switch) Global->runcmd("remove example"); else setupExampleCommand(); Switch = !Switch; }, 200, 2); #endif // COMMAND_REGISTRY_EXTRA #endif // DEBUG original(this); setupEnumCommand(); setupEchoCommand(); } #endif // DEBUG TClasslessInstanceHook(void, "?compile@BaseCommandBlock@@AEAAXAEBVCommandOrigin@@AEAVLevel@@@Z", class CommandOrigin const& origin, class Level& level) { if (LL::globalConfig.tickThreadId != std::this_thread::get_id()) { SRWLockSharedHolder locker(delaySetupLock); return original(this, origin, level); } return original(this, origin, level); }