mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2024-11-08 00:37:15 +01:00
583 lines
21 KiB
C++
583 lines
21 KiB
C++
// ------------------------------------------------------------------------------------------------
|
|
#include "Core/Privilege.hpp"
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
namespace SqMod {
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
SQMOD_DECL_TYPENAME(ManagerTn, _SC("SqPrivilegeManager"))
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void TerminatePrivileges()
|
|
{
|
|
// Go over all managers and try to close them
|
|
for (PvManager * inst = PvManager::sHead; inst && inst->mNext != PvManager::sHead; inst = inst->mNext)
|
|
{
|
|
inst->Terminate();
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void PvManager::Release()
|
|
{
|
|
// Release objects from this instance
|
|
m_OnQuery.Release();
|
|
m_OnGained.Release();
|
|
m_OnLost.Release();
|
|
m_Tag.Release();
|
|
m_Data.Release();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void PvManager::Terminate()
|
|
{
|
|
// Release script objects held by units
|
|
for (auto & u : m_Units)
|
|
{
|
|
u.second->Release();
|
|
}
|
|
// Release script objects held by classes
|
|
for (auto & c : m_Classes)
|
|
{
|
|
c.second->Release();
|
|
}
|
|
// Release script objects held by entries
|
|
for (auto & e : m_Entries)
|
|
{
|
|
e.second->Release();
|
|
}
|
|
// Release script objects held by the manager
|
|
Release();
|
|
// Clear the containers as well
|
|
m_Classes.clear();
|
|
m_Units.clear();
|
|
m_Entries.clear();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
LightObj PvManager::CreateEntry(SQInteger id, StackStrF & name)
|
|
{
|
|
ModifyEntries();
|
|
// Look for a similar entry
|
|
auto itr = m_Entries.find(PvIdentity(id));
|
|
// Is this unique?
|
|
if (itr != m_Entries.end())
|
|
{
|
|
STHROWF("Entry ({} : {}) already exists", id, name.mPtr);
|
|
}
|
|
// We backup the name hash now because I don't know if the move will happen before or after i create the identity
|
|
// Compiler optimizations and sh!t. (the name hash is cached in .mRes member variable)
|
|
const auto h = name.CacheHash().GetHash();
|
|
// Create it now
|
|
auto & e = m_Entries.emplace_back(PvIdentity(id, h), std::make_shared< PvEntry >(id, std::move(name), this));
|
|
// Create a wrapper instance and return it
|
|
return LightObj(SqTypeIdentity< SqPvEntry >{}, SqVM(), e);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
LightObj PvManager::CreateClass(SQInteger id, StackStrF & name)
|
|
{
|
|
ModifyClasses();
|
|
// Look for a similar class
|
|
auto itr = m_Classes.find(PvIdentity(id));
|
|
// Is this unique?
|
|
if (itr != m_Classes.end())
|
|
{
|
|
STHROWF("Class ({} : {}) already exists", id, name.mPtr);
|
|
}
|
|
// We backup the name hash now because I don't know if the move will happen before or after i create the identity
|
|
// Compiler optimizations and sh!t. (the name hash is cached in .mRes member variable)
|
|
const auto h = name.CacheHash().GetHash();
|
|
// Create it now
|
|
auto & e = m_Classes.emplace_back(PvIdentity(id, h), std::make_shared< PvClass >(id, std::move(name), this));
|
|
// Create a wrapper instance and return it
|
|
return LightObj(SqTypeIdentity< SqPvClass >{}, SqVM(), e);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
LightObj PvManager::CreateUnit(SQInteger id, const SqPvClass & cls, StackStrF & name)
|
|
{
|
|
ModifyUnits();
|
|
// Validate the given class
|
|
cls.Validate();
|
|
// Look for a similar unit
|
|
auto itr = m_Units.find(PvIdentity(id));
|
|
// Is this unique?
|
|
if (itr != m_Units.end())
|
|
{
|
|
STHROWF("Unit ({} : {}) already exists", id, name.mPtr);
|
|
}
|
|
// We backup the name hash now because I don't know if the move will happen before or after i create the identity
|
|
// Compiler optimizations and sh!t. (the name hash is cached in .mRes member variable)
|
|
const auto h = name.CacheHash().GetHash();
|
|
// Create it now
|
|
auto & e = m_Units.emplace_back(PvIdentity(id, h), std::make_shared< PvUnit >(id, std::move(name), cls.mI));
|
|
// Create a wrapper instance and return it
|
|
return LightObj(SqTypeIdentity< SqPvUnit >{}, SqVM(), e);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void PvManager::PropagateParentAssign(const PvClass & cls, const PvClass::Ref & parent)
|
|
{
|
|
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void PvManager::PropagateParentChange(const PvClass & cls, const PvClass::Ref & parent)
|
|
{
|
|
// Prevent any changes to entries during this operation
|
|
AutoAssign< bool > aag(m_LockEntries, false, true);
|
|
// Go over all entries and see if the specified class will gain or loose any privileges from this change
|
|
for (const auto & e : m_Entries)
|
|
{
|
|
// Owned privileges are not affected regardless of the inherited class
|
|
{
|
|
// See if this class inherits the current entry
|
|
auto itr = cls.mPrivileges.find(e.second->mID);
|
|
// Is this entry inherited or owned?
|
|
if (itr != cls.mPrivileges.end())
|
|
{
|
|
continue; // Nothing new will happen here
|
|
}
|
|
}
|
|
// We know we inherit this entry value so let's get that value instead
|
|
SQInteger inherited = cls.GetInheritedEntryValue(e.second->mID);
|
|
// Get the value that will be inherited for this entry
|
|
SQInteger current = parent->GetEntryValue(e.second->mID);
|
|
// Are they literally the same?
|
|
if (inherited == current)
|
|
{
|
|
continue; // Don't even bother
|
|
}
|
|
// Find out who can identify this change
|
|
const Function & modify = e.second->mOnModify.IsNull() ? m_OnModify : e.second->mOnModify;
|
|
// Is there someone that can identify this change?
|
|
if (!modify.IsNull())
|
|
{
|
|
LightObj r = modify.Eval(inherited, current);
|
|
// Was this considered a change?
|
|
if (!r.IsNull())
|
|
{
|
|
cls.DoChanged(e.second->mID, r.Cast< bool >(), current);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// By default we use > comparison to decide upgrades
|
|
cls.DoChanged(e.second->mID, current > inherited, current);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void PvManager::PropagateClassChange(const PvUnit & unit, const PvClass::Ref & cls)
|
|
{
|
|
// Prevent any changes to entries during this operation
|
|
AutoAssign< bool > aag(m_LockEntries, false, true);
|
|
// Go over all entries and see if the specified unit will gain or loose any privileges from this change
|
|
for (const auto & e : m_Entries)
|
|
{
|
|
// Owned privileges are not affected regardless of the inherited class
|
|
{
|
|
// See if this unit inherits the current entry
|
|
auto itr = unit.mPrivileges.find(e.second->mID);
|
|
// Is this entry inherited or owned?
|
|
if (itr != unit.mPrivileges.end())
|
|
{
|
|
continue; // Nothing new will happen here
|
|
}
|
|
}
|
|
// We know we inherit this entry value so let's get that value instead
|
|
SQInteger inherited = unit.GetInheritedEntryValue(e.second->mID);
|
|
// Get the value that will be inherited for this entry
|
|
SQInteger current = cls->GetEntryValue(e.second->mID);
|
|
// Are they literally the same?
|
|
if (inherited == current)
|
|
{
|
|
continue; // Don't even bother
|
|
}
|
|
// Find out who can identify this change
|
|
const Function & modify = e.second->mOnModify.IsNull() ? m_OnModify : e.second->mOnModify;
|
|
// Is there someone that can identify this change?
|
|
if (!modify.IsNull())
|
|
{
|
|
LightObj r = modify.Eval(inherited, current);
|
|
// Was this considered a change?
|
|
if (!r.IsNull())
|
|
{
|
|
unit.DoChanged(e.second->mID, r.Cast< bool >(), current);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// By default we use > comparison to decide upgrades
|
|
unit.DoChanged(e.second->mID, current > inherited, current);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
SQMOD_NODISCARD LightObj PvManager::GetEntryWithID(SQInteger id)
|
|
{
|
|
return LightObj(SqTypeIdentity< SqPvEntry >{}, SqVM(), GetValidEntry(id));
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
SQMOD_NODISCARD LightObj PvManager::GetEntryWithTag(StackStrF & tag)
|
|
{
|
|
return LightObj(SqTypeIdentity< SqPvEntry >{}, SqVM(), GetValidEntryWithTag(tag.CacheHash()));
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
bool PvManager::HaveEntryWithID(SQInteger id)
|
|
{
|
|
for (const auto & e : m_Entries) // NOLINT(readability-use-anyofallof)
|
|
{
|
|
if (e.first.mID == id)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
// Not found
|
|
return false;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
bool PvManager::HaveEntryWithTag(StackStrF & tag)
|
|
{
|
|
const size_t h = tag.ToHash();
|
|
for (const auto & e : m_Entries) // NOLINT(readability-use-anyofallof)
|
|
{
|
|
if (e.first.mHash == h)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
// Not found
|
|
return false;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
SQMOD_NODISCARD LightObj PvManager::GetClassWithID(SQInteger id)
|
|
{
|
|
return LightObj(SqTypeIdentity< SqPvClass >{}, SqVM(), GetValidClass(id));
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
SQMOD_NODISCARD LightObj PvManager::GetClassWithTag(StackStrF & tag)
|
|
{
|
|
return LightObj(SqTypeIdentity< SqPvClass >{}, SqVM(), GetValidClassWithTag(tag.CacheHash()));
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
bool PvManager::HaveClassWithID(SQInteger id)
|
|
{
|
|
for (const auto & c : m_Classes) // NOLINT(readability-use-anyofallof)
|
|
{
|
|
if (c.first.mID == id)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
// Not found
|
|
return false;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
bool PvManager::HaveClassWithTag(StackStrF & tag)
|
|
{
|
|
const size_t h = tag.ToHash();
|
|
for (const auto & c : m_Classes) // NOLINT(readability-use-anyofallof)
|
|
{
|
|
if (c.first.mHash == h)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
// Not found
|
|
return false;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
SQMOD_NODISCARD LightObj PvManager::GetUnitWithID(SQInteger id)
|
|
{
|
|
return LightObj(SqTypeIdentity< SqPvUnit >{}, SqVM(), GetValidUnit(id));
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
SQMOD_NODISCARD LightObj PvManager::GetUnitWithTag(StackStrF & tag)
|
|
{
|
|
return LightObj(SqTypeIdentity< SqPvUnit >{}, SqVM(), GetValidUnitWithTag(tag.CacheHash()));
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
bool PvManager::HaveUnitWithID(SQInteger id)
|
|
{
|
|
for (const auto & u : m_Units) // NOLINT(readability-use-anyofallof)
|
|
{
|
|
if (u.first.mID == id)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
// Not found
|
|
return false;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
bool PvManager::HaveUnitWithTag(StackStrF & tag)
|
|
{
|
|
const size_t h = tag.ToHash();
|
|
for (const auto & u : m_Units) // NOLINT(readability-use-anyofallof)
|
|
{
|
|
if (u.first.mHash == h)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
// Not found
|
|
return false;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void PvManager::RemoveEntryWithID(SQInteger id)
|
|
{
|
|
ModifyEntries();
|
|
|
|
// Remove this entry from the units
|
|
for (const auto & u : m_Units)
|
|
{
|
|
u.second->mPrivileges.erase(id);
|
|
}
|
|
// Remove this entry from the classes
|
|
for (const auto & c : m_Units)
|
|
{
|
|
c.second->mPrivileges.erase(id);
|
|
}
|
|
// Finally remove it from the list
|
|
m_Entries.erase(PvIdentity(id));
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void PvManager::RemoveEntryWithTag(StackStrF & tag)
|
|
{
|
|
ModifyEntries();
|
|
|
|
PvEntry & e = *GetValidEntryWithTag(tag);
|
|
// Remove this entry from the units
|
|
for (const auto & u : m_Units)
|
|
{
|
|
u.second->mPrivileges.erase(e.mID);
|
|
}
|
|
// Remove this entry from the classes
|
|
for (const auto & c : m_Units)
|
|
{
|
|
c.second->mPrivileges.erase(e.mID);
|
|
}
|
|
// Finally remove it from the list
|
|
auto itr = std::find_if(m_Entries.cbegin(), m_Entries.cend(),
|
|
[h = tag.CacheHash().GetHash()](PvEntry::List::const_reference e) -> bool { return e.first.mHash == h; });
|
|
// Was this unit found?
|
|
if (itr != m_Entries.end())
|
|
{
|
|
m_Entries.erase(itr);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void PvManager::RemoveClassWithID(SqPvClass & sub, SQInteger id)
|
|
{
|
|
ModifyClasses();
|
|
|
|
PvClass::Ref cls = sub.mI.lock();
|
|
// Remove this entry from units
|
|
for (const auto & u : m_Units)
|
|
{
|
|
if (u.second->mClass.lock().get() == cls.get())
|
|
{
|
|
AutoAssign< bool > aag(m_LockUnits, false, true);
|
|
u.second->AssignClass(cls);
|
|
}
|
|
}
|
|
// Remove this entry from classes
|
|
for (const auto & c : m_Classes)
|
|
{
|
|
if (!c.second->mParent.expired() && c.second->mParent.lock().get() == cls.get())
|
|
{
|
|
AutoAssign< bool > aag(m_LockClasses, false, true);
|
|
c.second->AssignParent(cls);
|
|
}
|
|
}
|
|
// Finally remove it from the list
|
|
m_Classes.erase(PvIdentity(id));
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void PvManager::RemoveClassWithTag(SqPvClass & sub, StackStrF & tag)
|
|
{
|
|
ModifyClasses();
|
|
|
|
PvClass::Ref cls = sub.mI.lock();
|
|
// Remove this class from units
|
|
for (const auto & u : m_Units)
|
|
{
|
|
if (u.second->mClass.lock().get() == cls.get())
|
|
{
|
|
AutoAssign< bool > aag(m_LockUnits, false, true);
|
|
u.second->AssignClass(cls);
|
|
}
|
|
}
|
|
// Remove this class from classes
|
|
for (const auto & c : m_Classes)
|
|
{
|
|
if (!c.second->mParent.expired() && c.second->mParent.lock().get() == cls.get())
|
|
{
|
|
AutoAssign< bool > aag(m_LockClasses, false, true);
|
|
c.second->AssignParent(cls);
|
|
}
|
|
}
|
|
// Finally remove it from the list
|
|
auto itr = std::find_if(m_Classes.cbegin(), m_Classes.cend(),
|
|
[h = tag.CacheHash().GetHash()](PvClass::List::const_reference e) -> bool { return e.first.mHash == h; });
|
|
// Was this unit found?
|
|
if (itr != m_Classes.end())
|
|
{
|
|
m_Classes.erase(itr);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void PvManager::RemoveUnitWithID(SQInteger id)
|
|
{
|
|
ModifyUnits();
|
|
|
|
// Remove this class from classes
|
|
for (const auto & c : m_Classes)
|
|
{
|
|
c.second->mUnits.erase(PvIdentity(id));
|
|
}
|
|
// Finally remove it from the list
|
|
m_Units.erase(PvIdentity(id));
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void PvManager::RemoveUnitWithTag(StackStrF & tag)
|
|
{
|
|
ModifyUnits();
|
|
|
|
PvUnit & u = *GetValidUnitWithTag(tag);
|
|
// Remove this class from classes
|
|
for (const auto & c : m_Classes)
|
|
{
|
|
c.second->mUnits.erase(PvIdentity(u.mID));
|
|
}
|
|
// Finally remove it from the list
|
|
auto itr = std::find_if(m_Units.cbegin(), m_Units.cend(),
|
|
[h = tag.CacheHash().GetHash()](PvUnit::List::const_reference e) -> bool { return e.first.mHash == h; });
|
|
// Was this unit found?
|
|
if (itr != m_Units.end())
|
|
{
|
|
m_Units.erase(itr);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void PvManager::EachEntryID(Object & ctx, Function & func)
|
|
{
|
|
// Prevent any changes to entries during this operation
|
|
AutoAssign< bool > aag(m_LockEntries, false, true);
|
|
// Iterate entries and forward the ID to the callback
|
|
for (const auto & e : m_Entries)
|
|
{
|
|
func(ctx, e.second->mID);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void PvManager::EachClassID(Object & ctx, Function & func)
|
|
{
|
|
// Prevent any changes to classes during this operation
|
|
AutoAssign< bool > aag(m_LockClasses, false, true);
|
|
// Iterate classes and forward the ID to the callback
|
|
for (const auto & c : m_Classes)
|
|
{
|
|
func(ctx, c.second->mID);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void PvManager::EachUnitID(Object & ctx, Function & func)
|
|
{
|
|
// Prevent any changes to units during this operation
|
|
AutoAssign< bool > aag(m_LockUnits, false, true);
|
|
// Iterate units and forward the ID to the callback
|
|
for (const auto & u : m_Units)
|
|
{
|
|
func(ctx, u.second->mID);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
extern void Register_Privilege_Class(HSQUIRRELVM vm, Table & ns);
|
|
extern void Register_Privilege_Entry(HSQUIRRELVM vm, Table & ns);
|
|
extern void Register_Privilege_Unit(HSQUIRRELVM vm, Table & ns);
|
|
|
|
// ================================================================================================
|
|
void Register_Privilege(HSQUIRRELVM vm)
|
|
{
|
|
Table ns(vm);
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
Register_Privilege_Class(vm, ns);
|
|
Register_Privilege_Entry(vm, ns);
|
|
Register_Privilege_Unit(vm, ns);
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
ns.Bind(_SC("Manager"),
|
|
Class< PvManager, NoCopy< PvManager > >(vm, ManagerTn::Str)
|
|
// Constructors
|
|
.Ctor()
|
|
.Ctor< StackStrF & >()
|
|
// Meta-methods
|
|
.SquirrelFunc(_SC("_typename"), &ManagerTn::Fn)
|
|
.Func(_SC("_tostring"), &PvManager::ToString)
|
|
// Core Properties
|
|
.Prop(_SC("Tag"), &PvManager::GetTag, &PvManager::SetTag)
|
|
.Prop(_SC("Data"), &PvManager::GetData, &PvManager::SetData)
|
|
// Core Methods
|
|
.FmtFunc(_SC("SetTag"), &PvManager::ApplyTag)
|
|
.CbFunc(_SC("OnQuery"), &PvManager::SetOnQuery)
|
|
.CbFunc(_SC("OnLost"), &PvManager::SetOnLost)
|
|
.CbFunc(_SC("OnGained"), &PvManager::SetOnGained)
|
|
// Member Methods
|
|
.CbFunc(_SC("CreateEntry"), &PvManager::CreateEntry)
|
|
.CbFunc(_SC("CreateClass"), &PvManager::CreateClass)
|
|
.CbFunc(_SC("CreateUnit"), &PvManager::CreateUnit)
|
|
.Func(_SC("GetEntry"), &PvManager::GetEntryWithID)
|
|
.FmtFunc(_SC("GetEntryWithTag"), &PvManager::GetEntryWithTag)
|
|
.Func(_SC("HaveEntry"), &PvManager::HaveEntryWithID)
|
|
.FmtFunc(_SC("HaveEntryWithTag"), &PvManager::HaveEntryWithTag)
|
|
.Func(_SC("GetClass"), &PvManager::GetClassWithID)
|
|
.FmtFunc(_SC("GetClassWithTag"), &PvManager::GetClassWithTag)
|
|
.Func(_SC("HaveClass"), &PvManager::HaveClassWithID)
|
|
.FmtFunc(_SC("HaveClassWithTag"), &PvManager::HaveClassWithTag)
|
|
.Func(_SC("GetUnit"), &PvManager::GetUnitWithID)
|
|
.FmtFunc(_SC("GetUnitWithTag"), &PvManager::GetUnitWithTag)
|
|
.Func(_SC("HaveUnit"), &PvManager::HaveUnitWithID)
|
|
.FmtFunc(_SC("HaveUnitWithTag"), &PvManager::HaveUnitWithTag)
|
|
.Func(_SC("RemoveUnit"), &PvManager::RemoveUnitWithID)
|
|
.FmtFunc(_SC("RemoveUnitWithTag"), &PvManager::RemoveUnitWithTag)
|
|
.Func(_SC("RemoveEntry"), &PvManager::RemoveEntryWithID)
|
|
.FmtFunc(_SC("RemoveEntryWithTag"), &PvManager::RemoveEntryWithTag)
|
|
.Func(_SC("RemoveClass"), &PvManager::RemoveClassWithID)
|
|
.FmtFunc(_SC("RemoveClassWithTag"), &PvManager::RemoveClassWithTag)
|
|
.FmtFunc(_SC("EachEntryID"), &PvManager::EachEntryID)
|
|
.FmtFunc(_SC("EachClassID"), &PvManager::EachClassID)
|
|
.FmtFunc(_SC("EachUnitID"), &PvManager::EachUnitID)
|
|
);
|
|
|
|
RootTable(vm).Bind(_SC("SqPrivilege"), ns);
|
|
}
|
|
|
|
} // Namespace:: SqMod
|