mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-08 20:24:47 -04:00
Fix JKRArchive on 64-bit
They store the data address in the same struct that's directly loaded from archive files. This means it's impossible to expand to 64-bit directly, and instead we need to allocate the data pointers in a separate array.
This commit is contained in:
@@ -51,6 +51,12 @@ inline u16 read_big_endian_u16(void* ptr) {
|
||||
|
||||
extern u32 sCurrentDirID__10JKRArchive; // JKRArchive::sCurrentDirID
|
||||
|
||||
#if TARGET_PC
|
||||
#define JKAR_DATA(entry) getFileDataPointer(entry->index)
|
||||
#else
|
||||
#define JKAR_DATA(entry) entry->data
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @ingroup jsystem-jkernel
|
||||
*
|
||||
@@ -78,7 +84,16 @@ public:
|
||||
BE(u32) type_flags_and_name_offset;
|
||||
BE(u32) data_offset;
|
||||
BE(u32) data_size;
|
||||
#if TARGET_PC
|
||||
// Yes, they store the data pointer in the datastructure that's directly loaded from
|
||||
// archive files.
|
||||
// We can't expand this struct to fit a 64-bit pointer, so instead
|
||||
// we'll need to store this data in a separate array.
|
||||
// Store the *index* of this entry (!= file_id), so we can look up the real pointer easily.
|
||||
u32 index;
|
||||
#else
|
||||
void* data;
|
||||
#endif
|
||||
|
||||
u32 getNameOffset() const { return type_flags_and_name_offset & 0xFFFFFF; }
|
||||
u16 getNameHash() const { return name_hash; }
|
||||
@@ -131,6 +146,11 @@ protected:
|
||||
JKRArchive();
|
||||
JKRArchive(s32, EMountMode);
|
||||
|
||||
#if TARGET_PC
|
||||
void*& getFileDataPointer(int idx) const;
|
||||
void initFileDataPointers();
|
||||
#endif
|
||||
|
||||
public:
|
||||
bool getDirEntry(SDirEntry*, u32) const;
|
||||
void* getIdxResource(u32);
|
||||
@@ -189,6 +209,10 @@ public:
|
||||
/* 0x50 */ s32* mExpandedSize;
|
||||
/* 0x54 */ const char* mStringTable;
|
||||
|
||||
#if TARGET_PC
|
||||
void** mFileData;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/* 0x58 */ u32 field_0x58;
|
||||
/* 0x5C */ JKRCompression mCompression;
|
||||
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
private:
|
||||
/* 0x00 */ // vtable
|
||||
/* 0x04 */ // JKRArchive
|
||||
/* 0x64 */ int field_0x64;
|
||||
/* 0x64 */ intptr_t field_0x64;
|
||||
/* 0x68 */ JKRAramBlock* mAramPart;
|
||||
/* 0x6C */ int field_0x6c;
|
||||
/* 0x70 */ JKRDvdFile* mDvdFile;
|
||||
|
||||
@@ -31,8 +31,8 @@ JKRAramArchive::~JKRAramArchive() {
|
||||
if (mArcInfoBlock != NULL) {
|
||||
SDIFileEntry* entry = mFiles;
|
||||
for (int i = 0; i < mArcInfoBlock->num_file_entries; i++) {
|
||||
if (entry->data != NULL) {
|
||||
JKRFreeToHeap(mHeap, entry->data);
|
||||
if (JKAR_DATA(entry) != NULL) {
|
||||
JKRFreeToHeap(mHeap, JKAR_DATA(entry));
|
||||
}
|
||||
entry++;
|
||||
}
|
||||
@@ -135,6 +135,9 @@ bool JKRAramArchive::open(s32 entryNum) {
|
||||
mFiles = (SDIFileEntry*)((u8*)mArcInfoBlock + mArcInfoBlock->file_entry_offset);
|
||||
mStringTable = (char*)((u8*)mArcInfoBlock + mArcInfoBlock->string_table_offset);
|
||||
mExpandedSize = NULL;
|
||||
#if TARGET_PC
|
||||
initFileDataPointers();
|
||||
#endif
|
||||
|
||||
u8 compressedFiles = 0; // maybe a check for if the last file is compressed?
|
||||
|
||||
@@ -199,7 +202,7 @@ void* JKRAramArchive::fetchResource(SDIFileEntry* pEntry, u32* pOutSize) {
|
||||
}
|
||||
|
||||
JKRCompression compression = JKRConvertAttrToCompressionType(u8(pEntry->type_flags_and_name_offset >> 24));
|
||||
if (pEntry->data == NULL) {
|
||||
if (JKAR_DATA(pEntry) == NULL) {
|
||||
u32 size = JKRAramArchive::fetchResource_subroutine(
|
||||
pEntry->data_offset + mBlock->getAddress(), pEntry->data_size, mHeap, compression,
|
||||
&outBuf);
|
||||
@@ -209,7 +212,7 @@ void* JKRAramArchive::fetchResource(SDIFileEntry* pEntry, u32* pOutSize) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pEntry->data = outBuf;
|
||||
JKAR_DATA(pEntry) = outBuf;
|
||||
if (compression == COMPRESSION_YAZ0) {
|
||||
this->setExpandSize(pEntry, *pOutSize);
|
||||
}
|
||||
@@ -221,7 +224,7 @@ void* JKRAramArchive::fetchResource(SDIFileEntry* pEntry, u32* pOutSize) {
|
||||
}
|
||||
}
|
||||
|
||||
return pEntry->data;
|
||||
return JKAR_DATA(pEntry);
|
||||
}
|
||||
|
||||
void* JKRAramArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* pEntry,
|
||||
@@ -233,7 +236,7 @@ void* JKRAramArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry*
|
||||
}
|
||||
|
||||
JKRCompression compression = JKRConvertAttrToCompressionType(u8(pEntry->type_flags_and_name_offset >> 24));
|
||||
if (pEntry->data == NULL) {
|
||||
if (JKAR_DATA(pEntry) == NULL) {
|
||||
bufferSize = (s32)ALIGN_PREV(bufferSize, 0x20);
|
||||
size = JKRAramArchive::fetchResource_subroutine(pEntry->data_offset + mBlock->getAddress(),
|
||||
size, (u8*)buffer, bufferSize, compression);
|
||||
@@ -247,7 +250,7 @@ void* JKRAramArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry*
|
||||
if (size > bufferSize) {
|
||||
size = bufferSize;
|
||||
}
|
||||
JKRHeap::copyMemory(buffer, pEntry->data, size);
|
||||
JKRHeap::copyMemory(buffer, JKAR_DATA(pEntry), size);
|
||||
}
|
||||
|
||||
if (resourceSize != NULL) {
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
|
||||
#if TARGET_PC
|
||||
#include <cassert>
|
||||
#endif
|
||||
|
||||
u32 JKRArchive::sCurrentDirID;
|
||||
|
||||
JKRArchive::JKRArchive() {
|
||||
@@ -30,7 +34,14 @@ JKRArchive::JKRArchive(s32 entryNumber, JKRArchive::EMountMode mountMode) {
|
||||
}
|
||||
}
|
||||
|
||||
JKRArchive::~JKRArchive() {}
|
||||
JKRArchive::~JKRArchive() {
|
||||
#if TARGET_PC
|
||||
if (mFileData != nullptr) {
|
||||
mHeap->free(mFileData);
|
||||
mFileData = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool JKRArchive::isSameName(JKRArchive::CArcName& name, u32 nameOffset, u16 nameHash) const {
|
||||
u16 hash = name.getHash();
|
||||
@@ -143,7 +154,7 @@ JKRArchive::SDIFileEntry* JKRArchive::findNameResource(const char* name) const {
|
||||
JKRArchive::SDIFileEntry* JKRArchive::findPtrResource(const void* resource) const {
|
||||
SDIFileEntry* fileEntry = mFiles;
|
||||
for (int i = 0; i < mArcInfoBlock->num_file_entries; i++) {
|
||||
if (fileEntry->data == resource) {
|
||||
if (JKAR_DATA(fileEntry) == resource) {
|
||||
return fileEntry;
|
||||
}
|
||||
fileEntry++;
|
||||
@@ -225,3 +236,31 @@ u32 JKRArchive::getExpandSize(SDIFileEntry* fileEntry) const {
|
||||
|
||||
return mExpandedSize[index];
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
void*& JKRArchive::getFileDataPointer(int idx) const {
|
||||
assert(mArcInfoBlock);
|
||||
assert(idx < mArcInfoBlock->num_file_entries);
|
||||
assert(mFileData);
|
||||
|
||||
return mFileData[idx];
|
||||
}
|
||||
|
||||
void JKRArchive::initFileDataPointers() {
|
||||
assert(mArcInfoBlock);
|
||||
assert(mFiles);
|
||||
|
||||
if (mFileData != nullptr) {
|
||||
mHeap->free(mFileData);
|
||||
}
|
||||
|
||||
mFileData = static_cast<void**>(
|
||||
mHeap->alloc(mArcInfoBlock->num_file_entries * sizeof(void*), alignof(void*)));
|
||||
|
||||
memset(mFileData, 0, mArcInfoBlock->num_file_entries * sizeof(void*));
|
||||
|
||||
for (u32 i = 0; i < mArcInfoBlock->num_file_entries; i++) {
|
||||
mFiles[i].index = i;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -248,9 +248,9 @@ void JKRArchive::removeResourceAll() {
|
||||
if (mArcInfoBlock && mMountMode != MOUNT_MEM) {
|
||||
SDIFileEntry* fileEntry = mFiles;
|
||||
for (int i = 0; i < mArcInfoBlock->num_file_entries; i++) {
|
||||
if (fileEntry->data) {
|
||||
JKRFreeToHeap(mHeap, fileEntry->data);
|
||||
fileEntry->data = NULL;
|
||||
if (JKAR_DATA(fileEntry)) {
|
||||
JKRFreeToHeap(mHeap, JKAR_DATA(fileEntry));
|
||||
JKAR_DATA(fileEntry) = NULL;
|
||||
}
|
||||
fileEntry++;
|
||||
}
|
||||
@@ -263,7 +263,7 @@ bool JKRArchive::removeResource(void* resource) {
|
||||
if (fileEntry == NULL)
|
||||
return false;
|
||||
|
||||
fileEntry->data = NULL;
|
||||
JKAR_DATA(fileEntry) = NULL;
|
||||
JKRFreeToHeap(mHeap, resource);
|
||||
return true;
|
||||
}
|
||||
@@ -274,7 +274,7 @@ bool JKRArchive::detachResource(void* resource) {
|
||||
if (fileEntry == NULL)
|
||||
return false;
|
||||
|
||||
fileEntry->data = NULL;
|
||||
JKAR_DATA(fileEntry) = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ JKRCompArchive::~JKRCompArchive() {
|
||||
SDIFileEntry* file = mFiles;
|
||||
for (int i = 0; i < mArcInfoBlock->num_file_entries; i++) {
|
||||
u32 flags = file->type_flags_and_name_offset >> 24;
|
||||
if ((flags & 16) == 0 && file->data != NULL) {
|
||||
JKRFreeToHeap(mHeap, file->data);
|
||||
if ((flags & 16) == 0 && JKAR_DATA(file) != NULL) {
|
||||
JKRFreeToHeap(mHeap, JKAR_DATA(file));
|
||||
}
|
||||
|
||||
file++;
|
||||
@@ -176,6 +176,11 @@ bool JKRCompArchive::open(s32 entryNum) {
|
||||
field_0x6c = arcHeader->header_length + arcHeader->file_data_offset;
|
||||
break;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
initFileDataPointers();
|
||||
#endif
|
||||
|
||||
mExpandedSize = NULL;
|
||||
u8 compressedFiles = 0;
|
||||
SDIFileEntry *fileEntry = mFiles;
|
||||
@@ -229,17 +234,17 @@ void* JKRCompArchive::fetchResource(SDIFileEntry *fileEntry, u32 *pSize) {
|
||||
pSize = &ptrSize; // this makes barely any sense but ok
|
||||
}
|
||||
|
||||
if (fileEntry->data == NULL) {
|
||||
if (JKAR_DATA(fileEntry) == NULL) {
|
||||
u32 flag = fileEntry->type_flags_and_name_offset >> 0x18;
|
||||
if(flag & 0x10) {
|
||||
fileEntry->data = (void *)(field_0x64 + fileEntry->data_offset);
|
||||
JKAR_DATA(fileEntry) = (void *)(field_0x64 + fileEntry->data_offset);
|
||||
*pSize = size;
|
||||
}
|
||||
else if (flag & 0x20) {
|
||||
u8 *data;
|
||||
size = JKRAramArchive::fetchResource_subroutine(fileEntry->data_offset + mAramPart->getAddress() - mSizeOfMemPart, size, mHeap, compression, &data);
|
||||
*pSize = size;
|
||||
fileEntry->data = data;
|
||||
JKAR_DATA(fileEntry) = data;
|
||||
if(compression == COMPRESSION_YAZ0) {
|
||||
setExpandSize(fileEntry, *pSize);
|
||||
}
|
||||
@@ -250,7 +255,7 @@ void* JKRCompArchive::fetchResource(SDIFileEntry *fileEntry, u32 *pSize) {
|
||||
if (pSize != NULL) {
|
||||
*pSize = resSize;
|
||||
}
|
||||
fileEntry->data = data;
|
||||
JKAR_DATA(fileEntry) = data;
|
||||
if (compression == COMPRESSION_YAZ0) {
|
||||
setExpandSize(fileEntry, *pSize);
|
||||
}
|
||||
@@ -261,7 +266,7 @@ void* JKRCompArchive::fetchResource(SDIFileEntry *fileEntry, u32 *pSize) {
|
||||
*pSize = fileEntry->data_size;
|
||||
}
|
||||
}
|
||||
return fileEntry->data;
|
||||
return JKAR_DATA(fileEntry);
|
||||
}
|
||||
|
||||
|
||||
@@ -274,7 +279,7 @@ void *JKRCompArchive::fetchResource(void *data, u32 compressedSize, SDIFileEntry
|
||||
u32 fileFlag = fileEntry->type_flags_and_name_offset >> 0x18;
|
||||
int compression = JKRConvertAttrToCompressionType(u8(fileFlag));
|
||||
|
||||
if(fileEntry->data != NULL) {
|
||||
if(JKAR_DATA(fileEntry) != NULL) {
|
||||
if (compression == COMPRESSION_YAZ0) {
|
||||
u32 expandSize = getExpandSize(fileEntry);
|
||||
if (expandSize != 0) {
|
||||
@@ -286,7 +291,7 @@ void *JKRCompArchive::fetchResource(void *data, u32 compressedSize, SDIFileEntry
|
||||
fileSize = compressedSize;
|
||||
}
|
||||
|
||||
JKRHeap::copyMemory(data, fileEntry->data, fileSize);
|
||||
JKRHeap::copyMemory(data, JKAR_DATA(fileEntry), fileSize);
|
||||
size = fileSize;
|
||||
}
|
||||
else {
|
||||
@@ -318,12 +323,12 @@ void JKRCompArchive::removeResourceAll() {
|
||||
for (int i = 0; i < mArcInfoBlock->num_file_entries; i++) {
|
||||
int tmp = fileEntry->type_flags_and_name_offset >> 0x18;
|
||||
|
||||
if (fileEntry->data != NULL) {
|
||||
if (JKAR_DATA(fileEntry) != NULL) {
|
||||
if (!(tmp & 0x10)) {
|
||||
JKRFreeToHeap(mHeap, fileEntry->data);
|
||||
JKRFreeToHeap(mHeap, JKAR_DATA(fileEntry));
|
||||
}
|
||||
|
||||
fileEntry->data = NULL;
|
||||
JKAR_DATA(fileEntry) = NULL;
|
||||
}
|
||||
}
|
||||
fileEntry++;
|
||||
@@ -340,7 +345,7 @@ bool JKRCompArchive::removeResource(void* resource) {
|
||||
JKRFreeToHeap(mHeap, resource);
|
||||
}
|
||||
|
||||
fileEntry->data = NULL;
|
||||
JKAR_DATA(fileEntry) = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ JKRDvdArchive::~JKRDvdArchive() {
|
||||
SDIFileEntry* fileEntry = mFiles;
|
||||
int i = 0;
|
||||
for (; i < mArcInfoBlock->num_file_entries; i++) {
|
||||
if (fileEntry->data) {
|
||||
JKRFreeToHeap(mHeap, fileEntry->data);
|
||||
if (JKAR_DATA(fileEntry)) {
|
||||
JKRFreeToHeap(mHeap, JKAR_DATA(fileEntry));
|
||||
}
|
||||
fileEntry++;
|
||||
}
|
||||
@@ -96,6 +96,10 @@ bool JKRDvdArchive::open(s32 entryNum) {
|
||||
mStringTable = (char*)((intptr_t)&mArcInfoBlock->num_nodes + mArcInfoBlock->string_table_offset);
|
||||
mExpandedSize = NULL;
|
||||
|
||||
#if TARGET_PC
|
||||
initFileDataPointers();
|
||||
#endif
|
||||
|
||||
u8 useCompression;
|
||||
useCompression = 0;
|
||||
SDIFileEntry* fileEntry;
|
||||
@@ -148,7 +152,7 @@ void* JKRDvdArchive::fetchResource(SDIFileEntry* fileEntry, u32* returnSize) {
|
||||
}
|
||||
|
||||
JKRCompression fileCompression = JKRConvertAttrToCompressionType(u8(fileEntry->type_flags_and_name_offset >> 24));
|
||||
if (!fileEntry->data) {
|
||||
if (!JKAR_DATA(fileEntry)) {
|
||||
u8* resourcePtr;
|
||||
u32 resourceSize = fetchResource_subroutine(
|
||||
mEntryNum, mDataOffset + fileEntry->data_offset, fileEntry->data_size, mHeap,
|
||||
@@ -158,7 +162,7 @@ void* JKRDvdArchive::fetchResource(SDIFileEntry* fileEntry, u32* returnSize) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fileEntry->data = resourcePtr;
|
||||
JKAR_DATA(fileEntry) = resourcePtr;
|
||||
if (fileCompression == COMPRESSION_YAZ0) {
|
||||
setExpandSize(fileEntry, *returnSize);
|
||||
}
|
||||
@@ -170,7 +174,7 @@ void* JKRDvdArchive::fetchResource(SDIFileEntry* fileEntry, u32* returnSize) {
|
||||
}
|
||||
}
|
||||
|
||||
return fileEntry->data;
|
||||
return JKAR_DATA(fileEntry);
|
||||
}
|
||||
|
||||
void* JKRDvdArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* fileEntry,
|
||||
@@ -179,7 +183,7 @@ void* JKRDvdArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* f
|
||||
u32 size = fileEntry->data_size;
|
||||
JKRCompression fileCompression = JKRConvertAttrToCompressionType(u8(fileEntry->type_flags_and_name_offset >> 24));
|
||||
|
||||
if (!fileEntry->data) {
|
||||
if (!JKAR_DATA(fileEntry)) {
|
||||
bufferSize = (s32)ALIGN_PREV(bufferSize, 0x20);
|
||||
size = fetchResource_subroutine(mEntryNum, mDataOffset + fileEntry->data_offset,
|
||||
fileEntry->data_size, (u8*)buffer, bufferSize, fileCompression,
|
||||
@@ -196,7 +200,7 @@ void* JKRDvdArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* f
|
||||
size = bufferSize;
|
||||
}
|
||||
|
||||
JKRHeap::copyMemory(buffer, fileEntry->data, size);
|
||||
JKRHeap::copyMemory(buffer, JKAR_DATA(fileEntry), size);
|
||||
}
|
||||
|
||||
if (returnSize) {
|
||||
|
||||
@@ -95,6 +95,10 @@ bool JKRMemArchive::open(s32 entryNum, JKRArchive::EMountDirection mountDirectio
|
||||
mFiles = (SDIFileEntry *)((u8 *)&mArcInfoBlock->num_nodes + mArcInfoBlock->file_entry_offset);
|
||||
mStringTable = (char *)((u8 *)&mArcInfoBlock->num_nodes + mArcInfoBlock->string_table_offset);
|
||||
|
||||
#if TARGET_PC
|
||||
initFileDataPointers();
|
||||
#endif
|
||||
|
||||
mArchiveData =
|
||||
(u8 *)((uintptr_t)mArcHeader + mArcHeader->header_length + mArcHeader->file_data_offset);
|
||||
mIsOpen = true;
|
||||
@@ -120,20 +124,25 @@ bool JKRMemArchive::open(void* buffer, u32 bufferSize, JKRMemBreakFlag flag) {
|
||||
mIsOpen = (flag == JKRMEMBREAK_FLAG_UNKNOWN1) ? true : false; // mIsOpen might be u8
|
||||
mHeap = JKRHeap::findFromRoot(buffer);
|
||||
mCompression = COMPRESSION_NONE;
|
||||
|
||||
#if TARGET_PC
|
||||
initFileDataPointers();
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void* JKRMemArchive::fetchResource(SDIFileEntry* fileEntry, u32* resourceSize) {
|
||||
JUT_ASSERT(555, isMounted());
|
||||
if (!fileEntry->data) {
|
||||
fileEntry->data = mArchiveData + fileEntry->data_offset;
|
||||
if (!JKAR_DATA(fileEntry)) {
|
||||
JKAR_DATA(fileEntry) = mArchiveData + fileEntry->data_offset;
|
||||
}
|
||||
|
||||
if (resourceSize) {
|
||||
*resourceSize = fileEntry->data_size;
|
||||
}
|
||||
|
||||
return fileEntry->data;
|
||||
return JKAR_DATA(fileEntry);
|
||||
}
|
||||
|
||||
void* JKRMemArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* fileEntry,
|
||||
@@ -144,8 +153,8 @@ void* JKRMemArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* f
|
||||
srcLength = bufferSize;
|
||||
}
|
||||
|
||||
if (fileEntry->data != NULL) {
|
||||
memcpy(buffer, fileEntry->data, srcLength);
|
||||
if (JKAR_DATA(fileEntry) != NULL) {
|
||||
memcpy(buffer, JKAR_DATA(fileEntry), srcLength);
|
||||
} else {
|
||||
u8 flags = fileEntry->type_flags_and_name_offset >> 24;
|
||||
JKRCompression compression = JKRConvertAttrToCompressionType(flags);
|
||||
@@ -172,8 +181,8 @@ void JKRMemArchive::removeResourceAll(void) {
|
||||
// first fileEntry will clear/remove the resource data.
|
||||
SDIFileEntry* fileEntry = mFiles;
|
||||
for (int i = 0; i < mArcInfoBlock->num_file_entries; i++) {
|
||||
if (fileEntry->data) {
|
||||
fileEntry->data = NULL;
|
||||
if (JKAR_DATA(fileEntry)) {
|
||||
JKAR_DATA(fileEntry) = NULL;
|
||||
}
|
||||
}
|
||||
fileEntry++;
|
||||
@@ -186,7 +195,7 @@ bool JKRMemArchive::removeResource(void* resource) {
|
||||
if (!fileEntry)
|
||||
return false;
|
||||
|
||||
fileEntry->data = NULL;
|
||||
JKAR_DATA(fileEntry) = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user