Ihr standet auch schon vor dem Problem, das sich das Spiel einfach so verabschiedet - immer an der gleichen Stelle? Dann werdet ihr dieses Hilfsmittel lieben:
Das kleine Tool „Logic Protection“ überprüft die Parameter, die an die Logic-Funktionen übergeben werden, darauf, ob sie gültige Parameter erhalten. Es hängt sich dabei zwischen den Mapper und die eigentliche C-API, um häufige falsche Parameter abzufangen.
LogicProtection = { Types = {} };
-- format: value types (check is added for each name!); value type... means unknown number of value types (only at the end of the list)
-- [value type] is an optional value
-- each parameter can be named optionally
function LogicProtection:Init()
-- save old Logic
LogicProtection.OldLogic = Logic;
-- and set new Logic
Logic = setmetatable({}, self);
end
function LogicProtection:Parse()
self.ParsedParameters = {};
for i, v in ipairs(self.Declarations) do
-- get '('
local bracket = string.find(v, '%(');
assert(bracket, "invalid format " .. v);
local name = string.sub(v, 1, bracket - 1);
assert(string.sub(v, -1, -1) == ')', "invalid format " .. v);
local parameters = string.sub(v, bracket + 1, -2);
local paramInfo = self:ParseParameters(parameters);
self.ParsedParameters[name] = paramInfo;
end
end
local Lexer = {};
function Lexer:New(_parameters)
local val = {};
setmetatable(val, { __index = self });
val.Data = _parameters;
val.Position = 1;
return val;
end
function Lexer:Next()
self:LookAhead();
self.NextValue = self.LookAheadValue;
self.Token = self.LookAheadToken;
self.LookAheadValue = nil;
assert(self.Token);
return self.Token;
end
function Lexer:LookAhead()
if self.LookAheadValue then
assert(self.LookAheadToken);
return self.LookAheadToken;
end
-- get next char
repeat
ch = self.Data:sub(self.Position, self.Position);
self.Position = self.Position + 1;
until ch ~= ' ';
assert(ch ~= '(', "invalid format " .. self.Data .. "at position " .. self.Position);
if ch == '' then
self.LookAheadToken = 'none';
self.LookAheadValue = ch;
elseif ch == '[' or ch == ']' or ch == ',' then
self.LookAheadValue = ch;
self.LookAheadToken = ch;
elseif ch == '.' then
ch = self.Data:sub(self.Position - 1, self.Position + 1);
self.Position = self.Position + 2;
assert(ch == '...', "invalid token; '...' expected at position " .. self.Position - 3);
self.LookAheadValue = ch;
self.LookAheadToken = ch;
else
local name = self.Data:match("[%a_][%w_]*", self.Position - 1);
assert(name, "invalid token; 'name' expected at position " .. self.Position - 1);
self.Position = self.Position + #name - 1;
self.LookAheadValue = name;
self.LookAheadToken = 'name';
end
assert(self.LookAheadToken);
return self.LookAheadToken;
end
function Lexer:TestNext(_token)
if self:LookAhead() == _token then
self:Next();
return true;
end
return false;
end
function Lexer:AtEOF()
return self.Position > #self.Data and (not self.LookAheadValue or self.LookAheadToken == 'none');
end
function LogicProtection:ParseParameters(_parameters)
local lexer = Lexer:New(_parameters);
local paramInfo = { minParameters = 0, maxParameters = 0 };
local start = true;
while not lexer:AtEOF() do
self:ParseNextParameter(paramInfo, lexer, start);
start = false;
end
return paramInfo;
end
function LogicProtection:ParseNextParameter(_paramInfo, _lexer, _bFirst)
assert(not _paramInfo.isVararg, "vararg can only be at the end of a parameter list");
if _lexer:TestNext('[') then
if not _bFirst then
assert(_lexer:Next() == ',');
end
local i = #_paramInfo + 1;
local minNumberOfParameters = _paramInfo.minParameters;
local start = true;
repeat
self:ParseNextParameter(_paramInfo, _lexer, start);
start = false;
until _lexer:TestNext(']');
_paramInfo[i].isOptional = true;
_paramInfo[i].nextNonOptional = #_paramInfo + 1;
_paramInfo.minParameters = minNumberOfParameters;
return;
end
if not _bFirst then
assert(_lexer:Next() == ',', "invalid token " .. _lexer.Token .. " at " .. _lexer.Position);
end
assert(_lexer:Next() == 'name', "invalid token " .. _lexer.Token .. " at " .. _lexer.Position);
local entry = {};
assert(self.Types[_lexer.NextValue], "invalid type name in format " .. _lexer.Data);
Table_Copy(entry, self.Types[_lexer.NextValue]);
_paramInfo[#_paramInfo + 1] = entry;
if _lexer:TestNext('...') then
_paramInfo.maxParameters = -1;
_paramInfo.isVararg = true;
else
_paramInfo.minParameters = _paramInfo.minParameters + 1;
_paramInfo.maxParameters = _paramInfo.maxParameters + 1;
end
_lexer:TestNext('name'); -- ignore optional name for now
end
function LogicProtection:__index(_k)
return function(...) return LogicProtection:Protect(_k, ...); end
end
function LogicProtection:Protect(_k, ...)
local parameterInfo = self.ParsedParameters[_k];
if self.errorFlag then
return; -- do nothing!
end
if not parameterInfo then
return self.OldLogic[_k](...);
end
local minNumberOfParameters = parameterInfo.minParameters;
local maxNumberOfParameters = parameterInfo.maxParameters;
Framework.WriteToLog(_k .. ":" .. minNumberOfParameters);
if minNumberOfParameters > select("#", ...) then
if self.subsequentCall then
self.errorFlag = true;
return;
end
assert(false, _k .. ": not enough parameters");
end
if maxNumberOfParameters ~= -1 and maxNumberOfParameters < select("#", ...) then
if self.subsequentCall then
self.errorFlag = true;
return;
end
assert(false, _k .. ": too many parameters");
end
-- check each parameter
local iInParameterInfo = 1;
for i = 1, select("#", ...) do
local param = select(i, ...);
local paramInfo = parameterInfo[iInParameterInfo];
if not paramInfo then
if not parameterInfo.isVararg then
if self.subsequentCall then
self.errorFlag = true;
return;
else
assert(false, _k .. ": too many parameters");
end
end
paramInfo = parameterInfo[#parameterInfo];
end
repeat
local oldSubsequentCall = self.subsequentCall;
self.subsequentCall = true;
local res = paramInfo:check(param);
self.subsequentCall = oldSubsequentCall;
assert(res or paramInfo.isOptional, "invalid arg #" .. i .. "to " .. _k .. ": " .. paramInfo.type .. " expected " .. ", got " .. type(param));
if not res or self.errorFlag then
if paramInfo.isOptional then
self.errorFlag = false;
elseif not self.subsequentCall then
self.errorFlag = false;
assert(false, "invalid arg #" .. i .. "to " .. _k .. ": " .. paramInfo.type .. " expected " .. ", got " .. type(param));
else
return;
end
iInParameterInfo = paramInfo.nextNonOptional;
if not parameterInfo[iInParameterInfo] then
if self.subsequentCall then
self.errorFlag = true;
else
assert(false, "invalid arg #" .. i .. "to " .. _k .. ": " .. paramInfo.type .. " expected " .. ", got " .. type(param));
end
end
paramInfo = parameterInfo[iInParameterInfo]
end
until res;
iInParameterInfo = iInParameterInfo + 1;
end
return self.OldLogic[_k](...);
end
function LogicProtection:AddType(_name, _type)
_type.type = _name;
self.Types[_name] = _type;
return _type;
end
local function checkStandardType(_data, _param)
return type(_param) == _data.type;
end
local function addStandardType(_name)
return LogicProtection:AddType(_name, { check = checkStandardType });
end
addStandardType("nil");
addStandardType("boolean");
local numbersType = addStandardType("number");
addStandardType("string");
addStandardType("table");
addStandardType("function");
addStandardType("thread");
addStandardType("userdata");
-- some S6-specific types
LogicProtection:AddType("EntityID", {
check = function(_data, _param)
return not Logic.IsEntityDestroyed(_param);
end
});
LogicProtection:AddType("Amount", {
check = function(_data, _param)
return numbersType:check(_param) and _param >= 0;
end
});
LogicProtection:AddType("Good", {
check = function(_data, _param)
return Logic.GetGoodTypeName(_param);
end
});
LogicProtection:AddType("BuildingID", {
check = function(_data, _param)
return Logic.IsBuilding(_param) == 1;
end
});
LogicProtection:AddType("LeaderID", {
check = function(_data, _param)
return Logic.IsLeader(_param) == 1;
end
});
LogicProtection:AddType("WorkerID", {
check = function(_data, _param)
return Logic.IsWorker(_param);
end
});
LogicProtection:AddType("SpouseID", {
check = function(_data, _param)
return Logic.IsSpouse(_param);
end
});
LogicProtection:AddType("SoldierID", {
check = function(_data, _param)
return Logic.IsEntityInCategory(_param, EntityCategories.Soldier) == 1;
end
});
LogicProtection:AddType("StorehouseID", {
check = function(_data, _param)
return Logic.IsEntityInCategory(_param, EntityCategories.Storehouse) == 1;
end
});
LogicProtection:AddType("MarketplaceID", {
check = function(_data, _param)
return Logic.IsEntityInCategory(_param, EntityCategories.Marketplace) == 1;
end
});
LogicProtection:AddType("CastleID", {
check = function(_data, _param)
return Logic.IsEntityInCategory(_param, EntityCategories.Headquarters) == 1;
end
});
LogicProtection:AddType("HeavyWeaponID", {
check = function(_data, _param)
return Logic.IsEntityInCategory(_param, EntityCategories.HeavyWeapon) == 1;
end
});
LogicProtection:AddType("ThiefID", {
check = function(_data, _param)
return Logic.IsEntityInCategory(_param, EntityCategories.Thief) == 1;
end
});
LogicProtection:AddType("TheatreID", {
check = function(_data, _param)
return Logic.GetEntityType(_param) == Entities.B_Theatre;
end
});
LogicProtection:AddType("Player", {
check = function(_data, _param)
return numbersType:check(_param) and _param % 1 == 0 and _param >= 1 and _param <= 8;
end
});
LogicProtection:AddType("PlayerWithZero", {
check = function(_data, _param)
return numbersType:check(_param) and _param % 1 == 0 and _param >= 0 and _param <= 8;
end
});
LogicProtection:AddType("PositionX", {
check = function(_data, _param)
return numbersType:check(_param) and _param >= 0 and _param <= Logic.WorldGetSize();
end
});
LogicProtection:AddType("PositionY", {
check = function(_data, _param)
return numbersType:check(_param) and _param >= 0 and _param <= Logic.WorldGetSize();
end
});
LogicProtection:AddType("Buff", {
check = function(_data, _param)
local b = numbersType:check(_param);
if b then
for k, v in pairs(Buffs) do
if v == _param then
return true;
end
end
end
return false;
end
});
LogicProtection:AddType("EntityType", {
check = function(_data, _param)
local b = numbersType:check(_param);
if b then
for k, v in pairs(Entities) do
if v == _param then
return true;
end
end
end
return false;
end
});
LogicProtection:AddType("EffectType", {
check = function(_data, _param)
local b = numbersType:check(_param);
if b then
for k, v in pairs(EGL_Effects) do
if v == _param then
return true;
end
end
end
return false;
end
});
LogicProtection:AddType("SoldierType", {
check = function(_data, _param)
return Logic.IsEntityTypeInCategory(_param, EntityCategories.Soldier) == 1;
end
});
LogicProtection.Declarations = {
"AddBuff(Player player, Buff buff)",
"AddEntertainerTraderOffer(StorehouseID merchant, Amount numberOfOffers, Good _obsolete_goodForBuying, Amount _obsolete_Price, EntityType entertainer, Player player, Amount _obsolete_RefreshRate)",
"AddGoodToStock(EntityID entity, Good good, Amount amount [, boolean bInStock [, boolean bCreateStock]])",
"AddGoodTraderOffer(StorehouseID merchant, Amount numberOfOffers, Good _obsolete_goodForBuying, Amount _obsolete_Price, Good goodToSell, Amount goodAmount, Player player, Amount _obsolete_RefreshRate, EntityType marketer, EntityType resourceMerchant)",
"AddMercenaryTraderOffer(StorehouseID merchant, Amount numberOfOffers, Good _obsolete_goodForBuying, Amount _obsolete_Price, SoldierType soldier, Amount troopSize, Player player, Amount _obsolete_RefreshRate)",
"BuildingDoWorkersStrike(BuildingID building)",
"BuildingSetFireAlarm(BuildingID building, boolean alarmActive)",
"CanBeKnockedDown(BuildingID building)",
"CanBuildingBeSelectedForKnockDown(BuildingID building)",
"CanCancelKnockDownBuilding(BuildingID building)",
"CanCancelUpgradeBuilding(BuildingID building)",
"CanCreateCartFromSiegeEngine(Player player, HeavyWeaponID weapon, EntityType cartType)",
"CanCreateSiegeEngineFromCart(Player player, HeavyWeaponID weapon, EntityType cartType, boolean)",
"CanFireAlarmBeActivated(BuildingID building)",
"CanFitAnotherEntertainerOnMarketplace(MarketplaceID marketplace)",
"CanFitAnotherMerchantOnMarketplace(MarketplaceID marketplace)",
"CanHireEntertainer(Player player)",
"CanKidnapSpouse(LeaderID leader, SpouseID spouse)",
"CanProduceUnits(BuildingID building, EntityType toProduce)",
"CanRefillBattalion(LeaderID leader)",
"CanRepairAlarmBeActivated(BuildingID building)",
"CanSermonBeActivated(Player)",
"CanStartFestival(Player player, Amount festivalIndex)", -- first festival index is 0, so use type "Amount" for it as a small hack
"CanStartTheatrePlay(TheatreID)",
"CanThiefDeliverToCastle(ThiefID)",
"CanThiefStealBuilding(ThiefID)",
"ChangeEntityPlayerID(EntityID entity, PlayerWithZero player)",
"ChangeSettlerPlayerID(EntityID entity, Player player)",
"CheckBuildingPlacement()",
"CheckEntitiesDistance(EntityID id1, EntityID id2, Amount distance)",
"CheckSettlerPlacement()",
"CreateBattalion(SoldierType soldierType, PositionX posX, PositionY posY, number orientation, Player player [, Amount troopSize])",
"CreateBattalionOnUnblockedLand(SoldierType soldierType, PositionX posX, PositionY posY, number orientation, Player player [, Amount troopSize])",
"CreateEffect(EffectType eType, PositionX posX, PositionY posY [, Player player])",
"CreateEffectWithOrientation(EffectType eType, PositionX posX, PositionY posY, number orientationInRadians [, Player player])",
"CreateEntity(EntityType eType, PositionX posX, PositionY posY, number orientation [, PlayerWithZero player])",
"CreateEntityAtBuilding(EntityType eType, BuildingID building, number orientation [, PlayerWithZero player])",
"CreateEntityOnUnblockedLand(EntityType eType, PositionX posX, PositionY posY, number orientation [, PlayerWithZero player])",
"CreateReferenceToTableInGlobaLuaState(string tableName)",
"GetGoodTypeName(Amount good)", -- type "Amount" as a small hack... :-)
};
LogicProtection:Parse();
LogicProtection:Init();