write some shit for CKStateChunk
This commit is contained in:
parent
17333527ee
commit
50784e719c
|
@ -228,31 +228,35 @@ namespace LibCmo::CK2 {
|
|||
|
||||
/*
|
||||
A small hint for ToBeNotify, CommonToBeNotify and ToNotify
|
||||
Assume that A need notification from B, we can see it as A want buy something from B.
|
||||
Assume that A need notification from B, we can see it as that A want buy something from B.
|
||||
This relation is represented in ToBeNotify, a pure relation without any inhertance hierarchy.
|
||||
|
||||
Ok, now we assume A have children AA, B also have children BB.
|
||||
It's okey to order AA to buy something from B, becase AA is the children of A.
|
||||
This relation is represneted in CommonToBeNotify.
|
||||
Because B is a businessman, so his children BB also is a bussinessman.
|
||||
B and BB have the same goods so A can buy his stuff from both of B and BB.
|
||||
This is the first step executed by ComputeParentsNotifyTable().
|
||||
In this step, the function expand existing business relations to all possible business relations (expand to businessman's children)
|
||||
|
||||
Image there is a candy store, C. Because AA still is a kids.
|
||||
So AA want to buy something from C. Now C is in his ToBeNotify.
|
||||
Additionally, A, the parent of AA, force AA to buy something from B, just for A himself.
|
||||
For AA, he does not need want to buy something from B, but his parent A order he to do.
|
||||
So AA's ToBeNotify do not have B, but his CommonToBeNotify have B.
|
||||
This is the second step executed by ComputeParentsNotifyTable().
|
||||
For AA, his parent's relations also need to be merged after he processed his relations with C like I introduced previously.
|
||||
|
||||
Now, AA have a full business list writing all trades he can do.
|
||||
This is represented as CommonToBeNotify.
|
||||
In this time, AA's ToBeNotify only have C, but his CommonToBeNotify have B, BB and C.
|
||||
|
||||
So now, let we change the view from A to B.
|
||||
B now can sell something to A or AA. This represent in B's ToNotify.
|
||||
|
||||
Because B is a bussiness man, as the children of B, let we assume BB also have capability to sell something.
|
||||
He initially do not have any bussiness relation, because no one need to buy something from him.
|
||||
But he is smart, he want to use his parent bussiness relation. He copied his parent relation, B's ToNotify.
|
||||
Now BB can trade with A and AA, this represent in his ToNotify.
|
||||
|
||||
Now A and AA know that BB is children of B and B is their bussiness friend.
|
||||
So they also can buy something from BB. This result in that BB is added into their CommonToBeNotify.
|
||||
|
||||
B want to know whom I can sell something to.
|
||||
Because a full trades list are created from A side.
|
||||
The better solution is just ask the guest: do you want to buy something from me?
|
||||
This operation will fill ToNotify and is implemented at ComputeHierarchyTable().
|
||||
|
||||
At the end of this story,
|
||||
All bussiness man can use ToNofity to see whom they want to sell something to.
|
||||
ToNofity list have A and all of his children.
|
||||
And all buyer can use CommonToBeNofity to check who they can buy something from.
|
||||
CommonToBeNotify have B and all of this children.
|
||||
ToBeNotify is just a list to indicate the initial bussiness relation and will never used anymore after real relation established.
|
||||
*/
|
||||
|
||||
|
@ -308,33 +312,6 @@ namespace LibCmo::CK2 {
|
|||
// set done
|
||||
desc.Done = true;
|
||||
}
|
||||
//static void ComputeParentsNotifyTable(CKClassDesc& desc) {
|
||||
// // if it has done, do not process it again.
|
||||
// if (desc.Done) return;
|
||||
//
|
||||
// // find direct parent
|
||||
// CKClassDesc& parent = g_CKClassInfo[static_cast<size_t>(desc.Parent)];
|
||||
// if (!parent.IsValid) LIBPANIC("No such CK_CLASSID.");
|
||||
|
||||
// // if it is not self inheritance, call recursively
|
||||
// if (desc.Self != desc.Parent) {
|
||||
// ComputeParentsNotifyTable(parent);
|
||||
// }
|
||||
//
|
||||
// // copy parent ToNotify list
|
||||
// desc.ToNotify = parent.ToNotify;
|
||||
// // iterate all desc to know which id need beNotify me
|
||||
// // and add them
|
||||
// for (const auto& checking : g_CKClassInfo) {
|
||||
// if (!checking.IsValid) continue;
|
||||
// if (XContainer::NSXBitArray::IsSet(checking.CommonToBeNotify, static_cast<CKDWORD>(desc.Self))) {
|
||||
// XContainer::NSXBitArray::Set(desc.ToNotify, static_cast<CKDWORD>(checking.Self));
|
||||
// }
|
||||
// }
|
||||
|
||||
// // set done
|
||||
// desc.Done = true;
|
||||
//}
|
||||
static void CKBuildClassHierarchyTable() {
|
||||
size_t classCount = g_CKClassInfo.size();
|
||||
|
||||
|
|
|
@ -6,15 +6,6 @@ namespace LibCmo::CK2 {
|
|||
|
||||
#pragma region Ctor Dtor
|
||||
|
||||
//CKStateChunk::CKStateChunk() :
|
||||
// m_ClassId(CK_CLASSID::CKCID_OBJECT), m_DataDwSize(0u), m_pData(nullptr),
|
||||
// m_DataVersion(CK_STATECHUNK_DATAVERSION::CHUNKDATA_CURRENTVERSION), m_ChunkVersion(CK_STATECHUNK_CHUNKVERSION::CHUNK_VERSION4),
|
||||
// m_Parser{ CKStateChunkStatus::IDLE, 0u, 0u, 0u },
|
||||
// m_ObjectList(), m_ChunkList(), m_ManagerList(),
|
||||
// m_BindDoc(nullptr), m_BindContext(nullptr)
|
||||
//{
|
||||
// ;
|
||||
//}
|
||||
CKStateChunk::CKStateChunk(CKFileVisitor* visitor, CKContext* ctx) :
|
||||
m_ClassId(CK_CLASSID::CKCID_OBJECT), m_DataDwSize(0u), m_pData(nullptr),
|
||||
m_DataVersion(CK_STATECHUNK_DATAVERSION::CHUNKDATA_CURRENTVERSION), m_ChunkVersion(CK_STATECHUNK_CHUNKVERSION::CHUNK_VERSION4),
|
||||
|
@ -110,8 +101,10 @@ namespace LibCmo::CK2 {
|
|||
|
||||
#pragma region Misc Funcs
|
||||
|
||||
const ChunkProfile CKStateChunk::GetStateChunkProfile() {
|
||||
return ChunkProfile {
|
||||
// ========== Public Funcs ==========
|
||||
|
||||
const CKStateChunk::ProfileStateChunk_t CKStateChunk::GetStateChunkProfile() {
|
||||
return CKStateChunk::ProfileStateChunk_t {
|
||||
.m_ClassId = this->m_ClassId,
|
||||
.m_DataDwSize = this->m_DataDwSize,
|
||||
.m_pData = this->m_pData,
|
||||
|
@ -127,8 +120,41 @@ namespace LibCmo::CK2 {
|
|||
.m_BindContext = this->m_BindContext,
|
||||
};
|
||||
}
|
||||
|
||||
const XContainer::XArray<CKStateChunk::ProfileIdentifier_t> CKStateChunk::GetIdentifiersProfile() {
|
||||
XContainer::XArray<CKStateChunk::ProfileIdentifier_t> collection;
|
||||
if (this->m_Parser.m_Status != CKStateChunkStatus::READ) return collection;
|
||||
|
||||
void CKStateChunk::Clear(void) {
|
||||
CKDWORD pos = 0u;
|
||||
if (this->m_DataDwSize < 2u) return collection; // impossible to have a identifier
|
||||
|
||||
// iterate identifier
|
||||
while (true) {
|
||||
// add current identifier
|
||||
CKDWORD nextptr = this->m_pData[pos + 1];
|
||||
if (nextptr == 0u) {
|
||||
nextptr = this->m_DataDwSize; // got tail. no more identifier
|
||||
}
|
||||
collection.emplace_back(CKStateChunk::ProfileIdentifier_t{
|
||||
this->m_pData[pos],
|
||||
this->m_pData + pos + 2,
|
||||
CKSizeof(CKDWORD) * (nextptr - pos - 2u)
|
||||
});
|
||||
|
||||
// move to next identifier or exit
|
||||
// got tail. no more identifier
|
||||
if (this->m_pData[pos + 1] == 0u) break;
|
||||
|
||||
pos = this->m_pData[pos + 1];
|
||||
|
||||
// out of buffer
|
||||
if (pos + 1 >= this->m_DataDwSize) break;
|
||||
};
|
||||
return collection;
|
||||
|
||||
}
|
||||
|
||||
void CKStateChunk::Clear() {
|
||||
this->m_ClassId = CK_CLASSID::CKCID_OBJECT;
|
||||
this->m_DataVersion = CK_STATECHUNK_DATAVERSION::CHUNK_DEV_2_1;
|
||||
this->m_ChunkVersion = CK_STATECHUNK_CHUNKVERSION::CHUNK_VERSION4;
|
||||
|
@ -149,11 +175,11 @@ namespace LibCmo::CK2 {
|
|||
this->m_ChunkList.clear();
|
||||
}
|
||||
|
||||
CKDWORD CKStateChunk::GetDataSize(void) {
|
||||
CKDWORD CKStateChunk::GetDataSize() const {
|
||||
return CKSizeof(CKDWORD) * this->m_DataDwSize;
|
||||
}
|
||||
|
||||
CK_STATECHUNK_DATAVERSION CKStateChunk::GetDataVersion() {
|
||||
CK_STATECHUNK_DATAVERSION CKStateChunk::GetDataVersion() const {
|
||||
return this->m_DataVersion;
|
||||
}
|
||||
|
||||
|
@ -161,6 +187,14 @@ namespace LibCmo::CK2 {
|
|||
this->m_DataVersion = version;
|
||||
}
|
||||
|
||||
CK_CLASSID CKStateChunk::GetClassId() const {
|
||||
return this->m_ClassId;
|
||||
}
|
||||
|
||||
void CKStateChunk::SetClassId(CK_CLASSID cid) {
|
||||
this->m_ClassId = cid;
|
||||
}
|
||||
|
||||
bool CKStateChunk::Skip(CKDWORD DwordCount) {
|
||||
bool result;
|
||||
switch (this->m_Parser.m_Status) {
|
||||
|
@ -183,18 +217,7 @@ namespace LibCmo::CK2 {
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void* CKStateChunk::GetCurrentPointer() {
|
||||
switch (this->m_Parser.m_Status) {
|
||||
case CKStateChunkStatus::READ:
|
||||
case CKStateChunkStatus::WRITE:
|
||||
return this->m_pData + this->m_Parser.m_CurrentPos;
|
||||
case CKStateChunkStatus::IDLE:
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
// ========== Private Funcs ==========
|
||||
|
||||
CKDWORD CKStateChunk::GetCeilDwordSize(size_t char_size) {
|
||||
return static_cast<CKDWORD>((char_size + 3) >> 2);
|
||||
|
@ -448,33 +471,6 @@ namespace LibCmo::CK2 {
|
|||
|
||||
#pragma endregion
|
||||
|
||||
|
||||
//bool CKStateChunk::UnPack(CKDWORD DestSize) {
|
||||
// // NOTE: in UnPack. pData store the compressed buffer, and
|
||||
// // dwSize store the length of compressed buffer as CHAR size, not DWORD size!
|
||||
|
||||
// // create a enough buffer
|
||||
// CKBYTE* buffer = new CKBYTE[DestSize];
|
||||
// uLongf destSize = DestSize;
|
||||
// // uncompress it
|
||||
// auto err = uncompress(
|
||||
// reinterpret_cast<Bytef*>(buffer), &destSize,
|
||||
// reinterpret_cast<const Bytef*>(this->m_pData), static_cast<uLong>(this->m_DataDwSize)
|
||||
// );
|
||||
// // if no error, assign data
|
||||
// if (err == Z_OK) {
|
||||
// // get dw size and copy it to remove useless blank data
|
||||
// this->m_DataDwSize = static_cast<CKDWORD>(destSize) / CKSizeof(CKDWORD);
|
||||
|
||||
// delete[] this->m_pData;
|
||||
// this->m_pData = nullptr;
|
||||
// this->m_pData = new CKDWORD[this->m_DataDwSize];
|
||||
// std::memcpy(this->m_pData, buffer, this->m_DataDwSize * sizeof(CKDWORD));
|
||||
// }
|
||||
// delete[] buffer;
|
||||
// return true;
|
||||
//}
|
||||
|
||||
#pragma region Read Functions
|
||||
|
||||
void CKStateChunk::StartRead(void) {
|
||||
|
@ -529,38 +525,6 @@ namespace LibCmo::CK2 {
|
|||
return true;
|
||||
}
|
||||
|
||||
const XContainer::XArray<IdentifierProfile> CKStateChunk::GetIdentifierProfile() {
|
||||
XContainer::XArray<IdentifierProfile> collection;
|
||||
if (this->m_Parser.m_Status != CKStateChunkStatus::READ) return collection;
|
||||
|
||||
CKDWORD pos = 0u;
|
||||
if (this->m_DataDwSize < 2u) return collection; // impossible to have a identifier
|
||||
|
||||
// iterate identifier
|
||||
while (true) {
|
||||
// add current identifier
|
||||
CKDWORD nextptr = this->m_pData[pos + 1];
|
||||
if (nextptr == 0u) {
|
||||
nextptr = this->m_DataDwSize; // got tail. no more identifier
|
||||
}
|
||||
collection.emplace_back(IdentifierProfile{
|
||||
this->m_pData[pos],
|
||||
this->m_pData + pos + 2,
|
||||
CKSizeof(CKDWORD) * (nextptr - pos - 2u)
|
||||
});
|
||||
|
||||
// move to next identifier or exit
|
||||
// got tail. no more identifier
|
||||
if (this->m_pData[pos + 1] == 0u) break;
|
||||
|
||||
pos = this->m_pData[pos + 1];
|
||||
|
||||
// out of buffer
|
||||
if (pos + 1 >= this->m_DataDwSize) break;
|
||||
};
|
||||
return collection;
|
||||
|
||||
}
|
||||
|
||||
/* ========== Basic Data Read Functions ==========*/
|
||||
|
||||
|
|
|
@ -6,25 +6,6 @@
|
|||
|
||||
namespace LibCmo::CK2 {
|
||||
|
||||
struct ChunkProfile {
|
||||
CK_CLASSID m_ClassId;
|
||||
CKDWORD m_DataDwSize;
|
||||
CKDWORD* m_pData;
|
||||
|
||||
CK_STATECHUNK_DATAVERSION m_DataVersion;
|
||||
CK_STATECHUNK_CHUNKVERSION m_ChunkVersion;
|
||||
|
||||
size_t m_ObjectListSize, m_ChunkListSize, m_ManagerListSize;
|
||||
|
||||
CKFileVisitor* m_BindFile;
|
||||
CKContext* m_BindContext;
|
||||
};
|
||||
struct IdentifierProfile {
|
||||
CKDWORD m_Identifier;
|
||||
void* m_DataPtr;
|
||||
CKDWORD m_AreaSize;
|
||||
};
|
||||
|
||||
class CKStateChunk {
|
||||
public:
|
||||
//CKStateChunk();
|
||||
|
@ -35,6 +16,91 @@ namespace LibCmo::CK2 {
|
|||
CKStateChunk& operator=(CKStateChunk&&);
|
||||
~CKStateChunk();
|
||||
|
||||
#pragma region Self Used Data Struct
|
||||
|
||||
public:
|
||||
struct ProfileStateChunk_t {
|
||||
CK_CLASSID m_ClassId;
|
||||
CKDWORD m_DataDwSize;
|
||||
CKDWORD* m_pData;
|
||||
|
||||
CK_STATECHUNK_DATAVERSION m_DataVersion;
|
||||
CK_STATECHUNK_CHUNKVERSION m_ChunkVersion;
|
||||
|
||||
size_t m_ObjectListSize, m_ChunkListSize, m_ManagerListSize;
|
||||
|
||||
CKFileVisitor* m_BindFile;
|
||||
CKContext* m_BindContext;
|
||||
};
|
||||
struct ProfileIdentifier_t {
|
||||
CKDWORD m_Identifier;
|
||||
void* m_DataPtr;
|
||||
CKDWORD m_AreaSize;
|
||||
};
|
||||
template<bool _TCanWrite>
|
||||
class LockedBuffer_t {
|
||||
public:
|
||||
/**
|
||||
* @brief The type of free function called by this class.
|
||||
* It must have 2 argument, first one is buffer need to be free, second is consumed size.
|
||||
*/
|
||||
using FreeFct = std::function<void(const void*,CKDWORD)>;
|
||||
|
||||
LockedBuffer_t() : m_Ptr(nullptr), m_ConsumedByteSize(0), m_FreeFct(nullptr) {}
|
||||
LockedBuffer_t(const void* ptr, CKDWORD expectedByteSize, FreeFct fct) :
|
||||
m_Ptr(const_cast<CKBYTE*>(static_cast<const CKBYTE*>(ptr))), m_ConsumedByteSize(expectedByteSize), m_FreeFct(fct) {}
|
||||
LockedBuffer_t(const LockedBuffer_t&) = delete;
|
||||
LockedBuffer_t& operator=(const LockedBuffer_t&) = delete;
|
||||
LockedBuffer_t(LockedBuffer_t&& rhs) :
|
||||
m_Ptr(rhs.m_Ptr), m_ConsumedByteSize(rhs.m_ConsumedByteSize), m_FreeFct(rhs.m_FreeFct) {
|
||||
rhs.m_Ptr = nullptr;
|
||||
rhs.m_ConsumedByteSize = 0;
|
||||
rhs.m_FreeFct = nullptr;
|
||||
}
|
||||
LockedBuffer_t& operator=(LockedBuffer_t&& rhs) {
|
||||
// reset self
|
||||
Reset();
|
||||
// copy data
|
||||
m_Ptr = rhs.m_Ptr;
|
||||
m_ConsumedByteSize = rhs.m_ConsumedByteSize;
|
||||
m_FreeFct = rhs.m_FreeFct;
|
||||
// remove rhs data.
|
||||
rhs.m_Ptr = nullptr;
|
||||
rhs.m_ConsumedByteSize = 0;
|
||||
rhs.m_FreeFct = nullptr;
|
||||
}
|
||||
~LockedBuffer_t() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
const void* GetPtr() const {
|
||||
return m_Ptr;
|
||||
}
|
||||
// References:
|
||||
// https://stackoverflow.com/questions/43051882/how-to-disable-a-class-member-function-for-certain-template-types
|
||||
// https://stackoverflow.com/questions/13964447/why-compile-error-with-enable-if
|
||||
template<bool _UCanWrite = _TCanWrite, std::enable_if_t<sizeof(_UCanWrite) && _TCanWrite, int> = 0>
|
||||
void* GetMutablePtr() {
|
||||
return m_Ptr;
|
||||
}
|
||||
void SetRealConsumedSize(CKDWORD sizeInByte) {
|
||||
m_ConsumedByteSize = sizeInByte;
|
||||
}
|
||||
void Reset() {
|
||||
if (m_Ptr == nullptr) return;
|
||||
if (m_FreeFct == nullptr) return;
|
||||
m_FreeFct(m_Ptr, m_ConsumedByteSize);
|
||||
|
||||
m_Ptr = nullptr;
|
||||
m_ConsumedByteSize = 0;
|
||||
m_FreeFct = nullptr;
|
||||
}
|
||||
private:
|
||||
CKBYTE* m_Ptr;
|
||||
CKDWORD m_ConsumedByteSize;
|
||||
FreeFct m_FreeFct;
|
||||
};
|
||||
|
||||
private:
|
||||
enum class CKStateChunkStatus : CKDWORD {
|
||||
IDLE,
|
||||
|
@ -48,6 +114,10 @@ namespace LibCmo::CK2 {
|
|||
CKDWORD m_PrevIdentifierPos;
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Member Decl
|
||||
|
||||
CK_CLASSID m_ClassId;
|
||||
CKDWORD m_DataDwSize;
|
||||
CKDWORD* m_pData;
|
||||
|
@ -64,6 +134,8 @@ namespace LibCmo::CK2 {
|
|||
CKFileVisitor* m_BindFile;
|
||||
CKContext* m_BindContext;
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Buffer Related
|
||||
|
||||
public:
|
||||
|
@ -75,25 +147,66 @@ namespace LibCmo::CK2 {
|
|||
#pragma region Misc Functions
|
||||
|
||||
public:
|
||||
const ChunkProfile GetStateChunkProfile();
|
||||
// ===== 2 Profiles Getter used by Unvirt =====
|
||||
|
||||
//bool UnPack(CKDWORD DestSize);
|
||||
void Clear(void);
|
||||
CKDWORD GetDataSize(void);
|
||||
CK_STATECHUNK_DATAVERSION GetDataVersion();
|
||||
/**
|
||||
* @brief Get basic info of this CKStateChunk.
|
||||
* @return The struct describing this CKStateChunk.
|
||||
*/
|
||||
const ProfileStateChunk_t GetStateChunkProfile();
|
||||
/**
|
||||
* @brief Get all indentifier infos of this CKStateChunk,
|
||||
* including identifier self, data area size and address.
|
||||
* @return A arrary, each item describe a single identifier's info.
|
||||
* @remark The detail of implement can be seen in SeekIdentifierAndReturnSize()
|
||||
* @see SeekIdentifierAndReturnSize
|
||||
*/
|
||||
const XContainer::XArray<ProfileIdentifier_t> GetIdentifiersProfile();
|
||||
|
||||
/**
|
||||
* @brief Clear this CKStateChunk, restore it to original status for other using.
|
||||
*/
|
||||
void Clear();
|
||||
/**
|
||||
* @brief Get the size of data buffer in this CKStateChunk.
|
||||
* @return The data buffer's size in CKBYTE.
|
||||
*/
|
||||
CKDWORD GetDataSize() const;
|
||||
/**
|
||||
* @brief Get data version in this CKStateChunk.
|
||||
* @remark Data version is frequently used by calling of CKStateChunk to distinguish different data layout due to compatibility reason.
|
||||
* @return The data version.
|
||||
*/
|
||||
CK_STATECHUNK_DATAVERSION GetDataVersion() const;
|
||||
/**
|
||||
* @brief Set data version of this CKStateChunk.
|
||||
* @param version The set data version
|
||||
* @remark Frequently used when saving someing in CKStateChunk.
|
||||
*/
|
||||
void SetDataVersion(CK_STATECHUNK_DATAVERSION version);
|
||||
/**
|
||||
* @brief Get associated CK_CLASSID of this CKStateChunk
|
||||
* @return Associated CK_CLASSID
|
||||
*/
|
||||
CK_CLASSID GetClassId() const;
|
||||
/**
|
||||
* @brief Set associated CK_CLASSID of this CKStateChunk.
|
||||
* @param cid The set CK_CLASSID
|
||||
* @remark Frequently used at the end of CKObject::Save() override.
|
||||
*/
|
||||
void SetClassId(CK_CLASSID cid);
|
||||
|
||||
|
||||
bool Skip(CKDWORD DwordCount);
|
||||
|
||||
private:
|
||||
CKDWORD GetCeilDwordSize(size_t char_size);
|
||||
void* GetCurrentPointer();
|
||||
bool ResizeBuffer(CKDWORD new_dwsize);
|
||||
bool EnsureWriteSpace(CKDWORD dwsize);
|
||||
bool EnsureReadSpace(CKDWORD dword_required);
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
||||
#pragma region Read Function
|
||||
|
||||
public:
|
||||
|
@ -112,11 +225,14 @@ namespace LibCmo::CK2 {
|
|||
inline bool SeekIdentifierAndReturnSize(TEnum enum_v, CKDWORD* out_size) {
|
||||
return SeekIdentifierDwordAndReturnSize(static_cast<CKDWORD>(enum_v), out_size);
|
||||
}
|
||||
const XContainer::XArray<IdentifierProfile> GetIdentifierProfile();
|
||||
|
||||
/* ========== Basic Data Read Functions ==========*/
|
||||
|
||||
private:
|
||||
|
||||
bool LockReadBuffer(void** ppData, CKDWORD expectedByteSize);
|
||||
bool UnLockReadBuffer(CKDWORD realConsumedByteSize);
|
||||
|
||||
/**
|
||||
* @brief The base read function for all data.
|
||||
* This function will check all read requirements.
|
||||
|
|
Loading…
Reference in New Issue
Block a user