Changeset - r28821:565dadbd1dcb
[Not reviewed]
master
0 2 0
Loïc Guilloux - 9 months ago 2024-02-27 17:16:21
glx22@users.noreply.github.com
Fix bf4b6696: [Script] Broken ScriptText circular reference detection (#12187)
2 files changed with 18 insertions and 17 deletions:
0 comments (0 inline, 0 general)
src/script/api/script_text.cpp
Show inline comments
 
@@ -157,49 +157,51 @@ SQInteger ScriptText::_set(HSQUIRRELVM v
 

	
 
	return this->_SetParam(k, vm);
 
}
 

	
 
std::string ScriptText::GetEncodedText()
 
{
 
	StringIDList seen_ids;
 
	ScriptTextList seen_texts;
 
	ParamList params;
 
	int param_count = 0;
 
	std::string result;
 
	auto output = std::back_inserter(result);
 
	this->_FillParamList(params);
 
	this->_GetEncodedText(output, param_count, seen_ids, params);
 
	this->_FillParamList(params, seen_texts);
 
	this->_GetEncodedText(output, param_count, params);
 
	if (param_count > SCRIPT_TEXT_MAX_PARAMETERS) throw Script_FatalError(fmt::format("{}: Too many parameters", GetGameStringName(this->string)));
 
	return result;
 
}
 

	
 
void ScriptText::_FillParamList(ParamList &params)
 
void ScriptText::_FillParamList(ParamList &params, ScriptTextList &seen_texts)
 
{
 
	if (std::find(seen_texts.begin(), seen_texts.end(), this) != seen_texts.end()) throw Script_FatalError(fmt::format("{}: Circular reference detected", GetGameStringName(this->string)));
 
	seen_texts.push_back(this);
 

	
 
	for (int i = 0; i < this->paramc; i++) {
 
		Param *p = &this->param[i];
 
		params.emplace_back(this->string, i, p);
 
		if (!std::holds_alternative<ScriptTextRef>(*p)) continue;
 
		std::get<ScriptTextRef>(*p)->_FillParamList(params);
 
		std::get<ScriptTextRef>(*p)->_FillParamList(params, seen_texts);
 
	}
 

	
 
	seen_texts.pop_back();
 
}
 

	
 
void ScriptText::ParamCheck::Encode(std::back_insert_iterator<std::string> &output)
 
{
 
	if (this->used) return;
 
	if (std::holds_alternative<std::string>(*this->param)) fmt::format_to(output, ":\"{}\"", std::get<std::string>(*this->param));
 
	if (std::holds_alternative<SQInteger>(*this->param)) fmt::format_to(output, ":{:X}", std::get<SQInteger>(*this->param));
 
	if (std::holds_alternative<ScriptTextRef>(*this->param)) fmt::format_to(output, ":{:X}", this->owner);
 
	this->used = true;
 
}
 

	
 
void ScriptText::_GetEncodedText(std::back_insert_iterator<std::string> &output, int &param_count, StringIDList &seen_ids, ParamSpan args)
 
void ScriptText::_GetEncodedText(std::back_insert_iterator<std::string> &output, int &param_count, ParamSpan args)
 
{
 
	const std::string &name = GetGameStringName(this->string);
 

	
 
	if (std::find(seen_ids.begin(), seen_ids.end(), this->string) != seen_ids.end()) throw Script_FatalError(fmt::format("{}: Circular reference detected", name));
 
	seen_ids.push_back(this->string);
 

	
 
	Utf8Encode(output, SCC_ENCODED);
 
	fmt::format_to(output, "{:X}", this->string);
 

	
 
	const StringParams &params = GetGameStringParams(this->string);
 

	
 
	size_t idx = 0;
 
@@ -231,13 +233,13 @@ void ScriptText::_GetEncodedText(std::ba
 
					p.Encode(output);
 
					break;
 
				}
 
				int count = 0;
 
				fmt::format_to(output, ":");
 
				ScriptTextRef &ref = std::get<ScriptTextRef>(*p.param);
 
				ref->_GetEncodedText(output, count, seen_ids, args.subspan(idx));
 
				ref->_GetEncodedText(output, count, args.subspan(idx));
 
				p.used = true;
 
				if (++count != cur_param.consumes) {
 
					ScriptLog::Error(fmt::format("{}({}): {{{}}} expects {} to be consumed, but {} consumes {}", name, param_count + 1, cur_param.cmd, cur_param.consumes - 1, GetGameStringName(ref->string), count - 1));
 
					/* Fill missing params if needed. */
 
					for (int i = count; i < cur_param.consumes; i++) fmt::format_to(output, ":0");
 
				}
 
@@ -252,14 +254,12 @@ void ScriptText::_GetEncodedText(std::ba
 
					p.Encode(output);
 
				}
 
		}
 

	
 
		param_count += cur_param.consumes;
 
	}
 

	
 
	seen_ids.pop_back();
 
}
 

	
 
const std::string Text::GetDecodedText()
 
{
 
	::SetDParamStr(0, this->GetEncodedText());
 
	return ::GetString(STR_JUST_RAW_STRING);
src/script/api/script_text.hpp
Show inline comments
 
@@ -126,13 +126,13 @@ public:
 
	 * @api -all
 
	 */
 
	std::string GetEncodedText() override;
 

	
 
private:
 
	using ScriptTextRef = ScriptObjectRef<ScriptText>;
 
	using StringIDList = std::vector<StringID>;
 
	using ScriptTextList = std::vector<ScriptText *>;
 
	using Param = std::variant<SQInteger, std::string, ScriptTextRef>;
 

	
 
	struct ParamCheck {
 
		StringID owner;
 
		int idx;
 
		Param *param;
 
@@ -152,23 +152,24 @@ private:
 

	
 
	/**
 
	 * Internal function to recursively fill a list of parameters.
 
	 * The parameters are added as _GetEncodedText used to encode them
 
	 *  before the addition of parameter validation.
 
	 * @param params The list of parameters to fill.
 
	 * @param seen_texts The list of seen ScriptText.
 
	 */
 
	void _FillParamList(ParamList &params);
 
	void _FillParamList(ParamList &params, ScriptTextList &seen_texts);
 

	
 
	/**
 
	 * Internal function for recursive calling this function over multiple
 
	 *  instances, while writing in the same buffer.
 
	 * @param output The output to write the encoded text to.
 
	 * @param param_count The number of parameters that are in the string.
 
	 * @param seen_ids The list of seen StringID.
 
	 * @param param_count The number of parameters that are consumed by the string.
 
	 * @param args The parameters to be consumed.
 
	 */
 
	void _GetEncodedText(std::back_insert_iterator<std::string> &output, int &param_count, StringIDList &seen_ids, ParamSpan args);
 
	void _GetEncodedText(std::back_insert_iterator<std::string> &output, int &param_count, ParamSpan args);
 

	
 
	/**
 
	 * Set a parameter, where the value is the first item on the stack.
 
	 */
 
	SQInteger _SetParam(int k, HSQUIRRELVM vm);
 
};
0 comments (0 inline, 0 general)