mirror of
https://github.com/fadden/ciderpress.git
synced 2024-11-23 11:33:58 +00:00
Fix copy & paste
Some of the code was mis-handling wide character filenames. A direct copy & paste should be using the 8-bit form of the filename, but that's a deeper fix. Also, changed some types to use explicit integer width specifiers.
This commit is contained in:
parent
be8d3a4911
commit
a5fa12b332
@ -17,7 +17,8 @@
|
|||||||
* "Open" and "Save As" dialogs do, because those want to choose normal files
|
* "Open" and "Save As" dialogs do, because those want to choose normal files
|
||||||
* only, while this wants to select a folder.
|
* only, while this wants to select a folder.
|
||||||
*
|
*
|
||||||
* TODO: Vista-style dialogs support folder selection.
|
* TODO: Vista-style dialogs support folder selection. Consider switching
|
||||||
|
* based on OS version.
|
||||||
*/
|
*/
|
||||||
class ChooseDirDialog : public CDialog {
|
class ChooseDirDialog : public CDialog {
|
||||||
public:
|
public:
|
||||||
|
@ -12,9 +12,9 @@
|
|||||||
#include "PasteSpecialDialog.h"
|
#include "PasteSpecialDialog.h"
|
||||||
|
|
||||||
|
|
||||||
static const WCHAR kClipboardFmtName[] = L"faddenSoft:CiderPress:v1";
|
static const WCHAR kClipboardFmtName[] = L"faddenSoft:CiderPress:v2";
|
||||||
const int kClipVersion = 1; // should match "vN" in fmt name
|
const int kClipVersion = 2; // should match "vN" in fmt name
|
||||||
const unsigned short kEntrySignature = 0x4350;
|
const uint16_t kEntrySignature = 0x4350;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Win98 is quietly dying on large (20MB-ish) copies. Everything
|
* Win98 is quietly dying on large (20MB-ish) copies. Everything
|
||||||
@ -48,10 +48,10 @@ const int kClipTextMult = 4; // CF_OEMTEXT, CF_LOCALE, CF_UNICODETEXT*2
|
|||||||
* File collection header.
|
* File collection header.
|
||||||
*/
|
*/
|
||||||
typedef struct FileCollection {
|
typedef struct FileCollection {
|
||||||
unsigned short version; // currently 1
|
uint16_t version; // currently 1
|
||||||
unsigned short dataOffset; // offset to start of data
|
uint16_t dataOffset; // offset to start of data
|
||||||
unsigned long length; // total length;
|
uint32_t length; // total length;
|
||||||
unsigned long count; // #of entries
|
uint32_t count; // #of entries
|
||||||
} FileCollection;
|
} FileCollection;
|
||||||
|
|
||||||
/* what kind of entry is this */
|
/* what kind of entry is this */
|
||||||
@ -68,24 +68,27 @@ typedef enum EntryKind {
|
|||||||
* One of these per entry in the collection.
|
* One of these per entry in the collection.
|
||||||
*
|
*
|
||||||
* The next file starts at (start + dataOffset + dataLen + rsrcLen + cmmtLen).
|
* The next file starts at (start + dataOffset + dataLen + rsrcLen + cmmtLen).
|
||||||
|
*
|
||||||
|
* TODO: filename should be 8-bit from original, not 16-bit conversion
|
||||||
*/
|
*/
|
||||||
typedef struct FileCollectionEntry {
|
typedef struct FileCollectionEntry {
|
||||||
unsigned short signature; // let's be paranoid
|
uint16_t signature; // let's be paranoid
|
||||||
unsigned short dataOffset; // offset to start of data
|
uint16_t dataOffset; // offset to start of data
|
||||||
unsigned short fileNameLen; // len of (8-bit) filename, in bytes
|
uint16_t fileNameLen; // len of filename, in bytes
|
||||||
unsigned long dataLen; // len of data fork
|
uint32_t dataLen; // len of data fork
|
||||||
unsigned long rsrcLen; // len of rsrc fork
|
uint32_t rsrcLen; // len of rsrc fork
|
||||||
unsigned long cmmtLen; // len of comments
|
uint32_t cmmtLen; // len of comments
|
||||||
unsigned long fileType;
|
uint32_t fileType;
|
||||||
unsigned long auxType;
|
uint32_t auxType;
|
||||||
time_t createWhen;
|
int64_t createWhen; // time_t
|
||||||
time_t modWhen;
|
int64_t modWhen; // time_t
|
||||||
unsigned char access; // ProDOS access flags
|
uint8_t access; // ProDOS access flags
|
||||||
unsigned char entryKind; // GenericArchive::FileDetails::FileKind
|
uint8_t entryKind; // GenericArchive::FileDetails::FileKind
|
||||||
unsigned char sourceFS; // DiskImgLib::DiskImg::FSFormat
|
uint8_t sourceFS; // DiskImgLib::DiskImg::FSFormat
|
||||||
unsigned char fssep; // filesystem separator char, e.g. ':'
|
uint8_t fssep; // filesystem separator char, e.g. ':'
|
||||||
|
|
||||||
/* data comes next: filename, then data, then resource, then comment */
|
/* data comes next: null-terminated WCHAR filename, then data fork, then
|
||||||
|
resource fork, then comment */
|
||||||
} FileCollectionEntry;
|
} FileCollectionEntry;
|
||||||
|
|
||||||
|
|
||||||
@ -103,7 +106,7 @@ void MainWindow::OnEditCopy(void)
|
|||||||
bool isOpen = false;
|
bool isOpen = false;
|
||||||
HGLOBAL hGlobal;
|
HGLOBAL hGlobal;
|
||||||
LPVOID pGlobal;
|
LPVOID pGlobal;
|
||||||
unsigned char* buf = NULL;
|
uint8_t* buf = NULL;
|
||||||
long bufLen = -1;
|
long bufLen = -1;
|
||||||
|
|
||||||
/* associate a number with the format name */
|
/* associate a number with the format name */
|
||||||
@ -289,7 +292,7 @@ HGLOBAL MainWindow::CreateFileCollection(SelectionSet* pSelSet)
|
|||||||
|
|
||||||
if (pEntry->GetRecordKind() != GenericEntry::kRecordKindVolumeDir) {
|
if (pEntry->GetRecordKind() != GenericEntry::kRecordKindVolumeDir) {
|
||||||
totalLength += sizeof(FileCollectionEntry);
|
totalLength += sizeof(FileCollectionEntry);
|
||||||
totalLength += wcslen(pEntry->GetPathName()) +1;
|
totalLength += (wcslen(pEntry->GetPathName()) +1) * sizeof(WCHAR);
|
||||||
numFiles++;
|
numFiles++;
|
||||||
if (pEntry->GetRecordKind() != GenericEntry::kRecordKindDirectory) {
|
if (pEntry->GetRecordKind() != GenericEntry::kRecordKindDirectory) {
|
||||||
totalLength += (long) pEntry->GetDataForkLen();
|
totalLength += (long) pEntry->GetDataForkLen();
|
||||||
@ -361,7 +364,7 @@ HGLOBAL MainWindow::CreateFileCollection(SelectionSet* pSelSet)
|
|||||||
void* buf;
|
void* buf;
|
||||||
|
|
||||||
remainingLen = totalLength - sizeof(FileCollection);
|
remainingLen = totalLength - sizeof(FileCollection);
|
||||||
buf = (unsigned char*) pGlobal + sizeof(FileCollection);
|
buf = (uint8_t*) pGlobal + sizeof(FileCollection);
|
||||||
pSelSet->IterReset();
|
pSelSet->IterReset();
|
||||||
pSelEntry = pSelSet->IterNext();
|
pSelEntry = pSelSet->IterNext();
|
||||||
while (pSelEntry != NULL) {
|
while (pSelEntry != NULL) {
|
||||||
@ -384,7 +387,7 @@ HGLOBAL MainWindow::CreateFileCollection(SelectionSet* pSelSet)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ASSERT(remainingLen == 0);
|
ASSERT(remainingLen == 0);
|
||||||
ASSERT(buf == (unsigned char*) pGlobal + totalLength);
|
ASSERT(buf == (uint8_t*) pGlobal + totalLength);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Write the header.
|
* Write the header.
|
||||||
@ -421,8 +424,8 @@ CString MainWindow::CopyToCollection(GenericEntry* pEntry, void** pBuf,
|
|||||||
long* pBufLen)
|
long* pBufLen)
|
||||||
{
|
{
|
||||||
FileCollectionEntry collEnt;
|
FileCollectionEntry collEnt;
|
||||||
CString errStr, dummyStr;
|
CString errStr;
|
||||||
unsigned char* buf = (unsigned char*) *pBuf;
|
uint8_t* buf = (uint8_t*) *pBuf;
|
||||||
long remLen = *pBufLen;
|
long remLen = *pBufLen;
|
||||||
|
|
||||||
errStr.LoadString(IDS_CLIPBOARD_WRITEFAILURE);
|
errStr.LoadString(IDS_CLIPBOARD_WRITEFAILURE);
|
||||||
@ -457,25 +460,25 @@ CString MainWindow::CopyToCollection(GenericEntry* pEntry, void** pBuf,
|
|||||||
memset(&collEnt, 0x99, sizeof(collEnt));
|
memset(&collEnt, 0x99, sizeof(collEnt));
|
||||||
collEnt.signature = kEntrySignature;
|
collEnt.signature = kEntrySignature;
|
||||||
collEnt.dataOffset = sizeof(collEnt);
|
collEnt.dataOffset = sizeof(collEnt);
|
||||||
collEnt.fileNameLen = wcslen(pEntry->GetPathName()) +1;
|
collEnt.fileNameLen = (wcslen(pEntry->GetPathName()) +1) * sizeof(WCHAR);
|
||||||
if (pEntry->GetRecordKind() == GenericEntry::kRecordKindDirectory) {
|
if (pEntry->GetRecordKind() == GenericEntry::kRecordKindDirectory) {
|
||||||
collEnt.dataLen = collEnt.rsrcLen = collEnt.cmmtLen = 0;
|
collEnt.dataLen = collEnt.rsrcLen = collEnt.cmmtLen = 0;
|
||||||
} else {
|
} else {
|
||||||
collEnt.dataLen = (unsigned long) pEntry->GetDataForkLen();
|
collEnt.dataLen = (uint32_t) pEntry->GetDataForkLen();
|
||||||
collEnt.rsrcLen = (unsigned long) pEntry->GetRsrcForkLen();
|
collEnt.rsrcLen = (uint32_t) pEntry->GetRsrcForkLen();
|
||||||
collEnt.cmmtLen = 0; // CMMT FIX -- length unknown??
|
collEnt.cmmtLen = 0; // CMMT FIX -- length unknown??
|
||||||
}
|
}
|
||||||
collEnt.fileType = pEntry->GetFileType();
|
collEnt.fileType = pEntry->GetFileType();
|
||||||
collEnt.auxType = pEntry->GetAuxType();
|
collEnt.auxType = pEntry->GetAuxType();
|
||||||
collEnt.createWhen = pEntry->GetCreateWhen();
|
collEnt.createWhen = pEntry->GetCreateWhen();
|
||||||
collEnt.modWhen = pEntry->GetModWhen();
|
collEnt.modWhen = pEntry->GetModWhen();
|
||||||
collEnt.access = (unsigned char) pEntry->GetAccess();
|
collEnt.access = (uint8_t) pEntry->GetAccess();
|
||||||
collEnt.entryKind = (unsigned char) entryKind;
|
collEnt.entryKind = (uint8_t) entryKind;
|
||||||
collEnt.sourceFS = pEntry->GetSourceFS();
|
collEnt.sourceFS = pEntry->GetSourceFS();
|
||||||
collEnt.fssep = pEntry->GetFssep();
|
collEnt.fssep = pEntry->GetFssep();
|
||||||
|
|
||||||
/* verify there's enough space to hold everything */
|
/* verify there's enough space to hold everything */
|
||||||
if ((unsigned long) remLen < collEnt.fileNameLen +
|
if ((uint32_t) remLen < collEnt.fileNameLen +
|
||||||
collEnt.dataLen + collEnt.rsrcLen + collEnt.cmmtLen)
|
collEnt.dataLen + collEnt.rsrcLen + collEnt.cmmtLen)
|
||||||
{
|
{
|
||||||
ASSERT(false);
|
ASSERT(false);
|
||||||
@ -511,13 +514,15 @@ CString MainWindow::CopyToCollection(GenericEntry* pEntry, void** pBuf,
|
|||||||
else
|
else
|
||||||
which = GenericEntry::kDataThread;
|
which = GenericEntry::kDataThread;
|
||||||
|
|
||||||
|
CString extractErrStr;
|
||||||
result = pEntry->ExtractThreadToBuffer(which, &bufCopy, &lenCopy,
|
result = pEntry->ExtractThreadToBuffer(which, &bufCopy, &lenCopy,
|
||||||
&dummyStr);
|
&extractErrStr);
|
||||||
if (result == IDCANCEL) {
|
if (result == IDCANCEL) {
|
||||||
errStr.LoadString(IDS_CANCELLED);
|
errStr.LoadString(IDS_CANCELLED);
|
||||||
return errStr;
|
return errStr;
|
||||||
} else if (result != IDOK) {
|
} else if (result != IDOK) {
|
||||||
LOGI("ExtractThreadToBuffer (data) failed");
|
LOGW("ExtractThreadToBuffer (data) failed: %ls",
|
||||||
|
(LPCWSTR) extractErrStr);
|
||||||
return errStr;
|
return errStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -533,13 +538,15 @@ CString MainWindow::CopyToCollection(GenericEntry* pEntry, void** pBuf,
|
|||||||
lenCopy = remLen;
|
lenCopy = remLen;
|
||||||
which = GenericEntry::kRsrcThread;
|
which = GenericEntry::kRsrcThread;
|
||||||
|
|
||||||
|
CString extractErrStr;
|
||||||
result = pEntry->ExtractThreadToBuffer(which, &bufCopy, &lenCopy,
|
result = pEntry->ExtractThreadToBuffer(which, &bufCopy, &lenCopy,
|
||||||
&dummyStr);
|
&extractErrStr);
|
||||||
if (result == IDCANCEL) {
|
if (result == IDCANCEL) {
|
||||||
errStr.LoadString(IDS_CANCELLED);
|
errStr.LoadString(IDS_CANCELLED);
|
||||||
return errStr;
|
return errStr;
|
||||||
} else if (result != IDOK) {
|
} else if (result != IDOK) {
|
||||||
LOGI("ExtractThreadToBuffer (rsrc) failed");
|
LOGI("ExtractThreadToBuffer (rsrc) failed: %ls",
|
||||||
|
(LPCWSTR) extractErrStr);
|
||||||
return errStr;
|
return errStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -717,7 +724,7 @@ CString MainWindow::ProcessClipboard(const void* vbuf, long bufLen,
|
|||||||
{
|
{
|
||||||
FileCollection fileColl;
|
FileCollection fileColl;
|
||||||
CString errMsg, storagePrefix;
|
CString errMsg, storagePrefix;
|
||||||
const unsigned char* buf = (const unsigned char*) vbuf;
|
const uint8_t* buf = (const uint8_t*) vbuf;
|
||||||
DiskImgLib::A2File* pTargetSubdir = NULL;
|
DiskImgLib::A2File* pTargetSubdir = NULL;
|
||||||
XferFileOptions xferOpts;
|
XferFileOptions xferOpts;
|
||||||
bool xferPrepped = false;
|
bool xferPrepped = false;
|
||||||
@ -729,7 +736,7 @@ CString MainWindow::ProcessClipboard(const void* vbuf, long bufLen,
|
|||||||
* Pull the header out.
|
* Pull the header out.
|
||||||
*/
|
*/
|
||||||
if (bufLen < sizeof(fileColl)) {
|
if (bufLen < sizeof(fileColl)) {
|
||||||
LOGI("Clipboard contents too small!");
|
LOGW("Clipboard contents too small!");
|
||||||
goto bail;
|
goto bail;
|
||||||
}
|
}
|
||||||
memcpy(&fileColl, buf, sizeof(fileColl));
|
memcpy(&fileColl, buf, sizeof(fileColl));
|
||||||
@ -739,7 +746,7 @@ CString MainWindow::ProcessClipboard(const void* vbuf, long bufLen,
|
|||||||
* boundaries, which screws up our "bufLen > 0" while condition below.
|
* boundaries, which screws up our "bufLen > 0" while condition below.
|
||||||
*/
|
*/
|
||||||
if ((long) fileColl.length > bufLen) {
|
if ((long) fileColl.length > bufLen) {
|
||||||
LOGI("GLITCH: stored len=%ld, clip len=%ld",
|
LOGW("GLITCH: stored len=%ld, clip len=%ld",
|
||||||
fileColl.length, bufLen);
|
fileColl.length, bufLen);
|
||||||
goto bail;
|
goto bail;
|
||||||
}
|
}
|
||||||
@ -778,7 +785,7 @@ CString MainWindow::ProcessClipboard(const void* vbuf, long bufLen,
|
|||||||
|
|
||||||
if (pTargetSubdir != NULL) {
|
if (pTargetSubdir != NULL) {
|
||||||
storagePrefix = pTargetSubdir->GetPathName();
|
storagePrefix = pTargetSubdir->GetPathName();
|
||||||
LOGI("--- using storagePrefix '%ls'", (LPCWSTR) storagePrefix);
|
LOGD("--- using storagePrefix '%ls'", (LPCWSTR) storagePrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -795,11 +802,12 @@ CString MainWindow::ProcessClipboard(const void* vbuf, long bufLen,
|
|||||||
LOGI("+++ Starting paste, bufLen=%ld", bufLen);
|
LOGI("+++ Starting paste, bufLen=%ld", bufLen);
|
||||||
while (bufLen > 0) {
|
while (bufLen > 0) {
|
||||||
FileCollectionEntry collEnt;
|
FileCollectionEntry collEnt;
|
||||||
CString fileName, processErrStr;
|
CString fileName;
|
||||||
|
CString processErrStr;
|
||||||
|
|
||||||
/* read the entry info */
|
/* read the entry info */
|
||||||
if (bufLen < sizeof(collEnt)) {
|
if (bufLen < sizeof(collEnt)) {
|
||||||
LOGI("GLITCH: bufLen=%ld, sizeof(collEnt)=%d",
|
LOGW("GLITCH: bufLen=%ld, sizeof(collEnt)=%d",
|
||||||
bufLen, sizeof(collEnt));
|
bufLen, sizeof(collEnt));
|
||||||
ASSERT(false);
|
ASSERT(false);
|
||||||
goto bail;
|
goto bail;
|
||||||
@ -823,14 +831,15 @@ CString MainWindow::ProcessClipboard(const void* vbuf, long bufLen,
|
|||||||
ASSERT(false);
|
ASSERT(false);
|
||||||
goto bail;
|
goto bail;
|
||||||
}
|
}
|
||||||
fileName = buf;
|
// TODO: consider moving filename as raw 8-bit data
|
||||||
|
fileName = (const WCHAR*) buf;
|
||||||
buf += collEnt.fileNameLen;
|
buf += collEnt.fileNameLen;
|
||||||
bufLen -= collEnt.fileNameLen;
|
bufLen -= collEnt.fileNameLen;
|
||||||
|
|
||||||
//LOGI("+++ pasting '%s'", fileName);
|
LOGD("+++ pasting '%ls'", (LPCWSTR) fileName);
|
||||||
|
|
||||||
/* strip the path (if requested) and prepend the storage prefix */
|
/* strip the path (if requested) and prepend the storage prefix */
|
||||||
ASSERT(fileName.GetLength() == collEnt.fileNameLen -1);
|
ASSERT((fileName.GetLength() +1 ) * sizeof(WCHAR) == collEnt.fileNameLen);
|
||||||
if (pasteJunkPaths && collEnt.fssep != '\0') {
|
if (pasteJunkPaths && collEnt.fssep != '\0') {
|
||||||
int idx;
|
int idx;
|
||||||
idx = fileName.ReverseFind(collEnt.fssep);
|
idx = fileName.ReverseFind(collEnt.fssep);
|
||||||
@ -905,8 +914,8 @@ CString MainWindow::ProcessClipboardEntry(const FileCollectionEntry* pCollEnt,
|
|||||||
{
|
{
|
||||||
GenericArchive::FileDetails::FileKind entryKind;
|
GenericArchive::FileDetails::FileKind entryKind;
|
||||||
GenericArchive::FileDetails details;
|
GenericArchive::FileDetails details;
|
||||||
unsigned char* dataBuf = NULL;
|
uint8_t* dataBuf = NULL;
|
||||||
unsigned char* rsrcBuf = NULL;
|
uint8_t* rsrcBuf = NULL;
|
||||||
long dataLen, rsrcLen, cmmtLen;
|
long dataLen, rsrcLen, cmmtLen;
|
||||||
CString errMsg;
|
CString errMsg;
|
||||||
|
|
||||||
@ -915,7 +924,7 @@ CString MainWindow::ProcessClipboardEntry(const FileCollectionEntry* pCollEnt,
|
|||||||
|
|
||||||
details.entryKind = entryKind;
|
details.entryKind = entryKind;
|
||||||
details.origName = L"Clipboard";
|
details.origName = L"Clipboard";
|
||||||
details.storageName = pathName;
|
details.storageName = pathName; // TODO MacRoman convert
|
||||||
details.fileSysFmt = (DiskImg::FSFormat) pCollEnt->sourceFS;
|
details.fileSysFmt = (DiskImg::FSFormat) pCollEnt->sourceFS;
|
||||||
details.fileSysInfo = pCollEnt->fssep;
|
details.fileSysInfo = pCollEnt->fssep;
|
||||||
details.access = pCollEnt->access;
|
details.access = pCollEnt->access;
|
||||||
@ -970,11 +979,11 @@ CString MainWindow::ProcessClipboardEntry(const FileCollectionEntry* pCollEnt,
|
|||||||
|
|
||||||
if (hasData) {
|
if (hasData) {
|
||||||
if (pCollEnt->dataLen == 0) {
|
if (pCollEnt->dataLen == 0) {
|
||||||
dataBuf = new unsigned char[1];
|
dataBuf = new uint8_t[1];
|
||||||
dataLen = 0;
|
dataLen = 0;
|
||||||
} else {
|
} else {
|
||||||
dataLen = pCollEnt->dataLen;
|
dataLen = pCollEnt->dataLen;
|
||||||
dataBuf = new unsigned char[dataLen];
|
dataBuf = new uint8_t[dataLen];
|
||||||
if (dataBuf == NULL)
|
if (dataBuf == NULL)
|
||||||
return "memory allocation failed.";
|
return "memory allocation failed.";
|
||||||
memcpy(dataBuf, buf, dataLen);
|
memcpy(dataBuf, buf, dataLen);
|
||||||
@ -988,11 +997,11 @@ CString MainWindow::ProcessClipboardEntry(const FileCollectionEntry* pCollEnt,
|
|||||||
|
|
||||||
if (hasRsrc) {
|
if (hasRsrc) {
|
||||||
if (pCollEnt->rsrcLen == 0) {
|
if (pCollEnt->rsrcLen == 0) {
|
||||||
rsrcBuf = new unsigned char[1];
|
rsrcBuf = new uint8_t[1];
|
||||||
rsrcLen = 0;
|
rsrcLen = 0;
|
||||||
} else {
|
} else {
|
||||||
rsrcLen = pCollEnt->rsrcLen;
|
rsrcLen = pCollEnt->rsrcLen;
|
||||||
rsrcBuf = new unsigned char[rsrcLen];
|
rsrcBuf = new uint8_t[rsrcLen];
|
||||||
if (rsrcBuf == NULL)
|
if (rsrcBuf == NULL)
|
||||||
return "Memory allocation failed.";
|
return "Memory allocation failed.";
|
||||||
memcpy(rsrcBuf, buf, rsrcLen);
|
memcpy(rsrcBuf, buf, rsrcLen);
|
||||||
|
@ -520,6 +520,10 @@ public:
|
|||||||
*
|
*
|
||||||
* It's based on the NuFileDetails class from NufxLib (which used to be
|
* It's based on the NuFileDetails class from NufxLib (which used to be
|
||||||
* used everywhere).
|
* used everywhere).
|
||||||
|
*
|
||||||
|
* TODO: fix this
|
||||||
|
* TODO: may want to hold a raw 8-bit pathname for copy & paste, which
|
||||||
|
* doesn't need to convert in and out of MacRoman
|
||||||
*/
|
*/
|
||||||
class FileDetails {
|
class FileDetails {
|
||||||
public:
|
public:
|
||||||
|
Loading…
Reference in New Issue
Block a user