mirror of
https://github.com/rvtr/ctr_test_tools.git
synced 2025-06-19 09:05:31 -04:00
FalsifyTwlBackup:BkpType Legacy に対応。
git-svn-id: file:///Volumes/Transfer/gigaleak_20231201/2020-09-30%20-%20paladin.7z/paladin/ctr_test_tools@38 6b0af911-cb57-b745-895f-eec5701120e1
This commit is contained in:
parent
4978389346
commit
12cf32bf6b
@ -23,6 +23,10 @@ namespace TwlBackupBlock
|
|||||||
// for WithPrivateSave
|
// for WithPrivateSave
|
||||||
public const int RESERVED_SIZE_FOR_WPS = 12;
|
public const int RESERVED_SIZE_FOR_WPS = 12;
|
||||||
|
|
||||||
|
// for Legacy
|
||||||
|
public const int RESERVED_SIZE_FOR_LEGACY = 4;
|
||||||
|
public const int NUM_OF_SECTION_LEGACY = 4;
|
||||||
|
|
||||||
public UInt32 signature;
|
public UInt32 signature;
|
||||||
public UInt16 companyCode;
|
public UInt16 companyCode;
|
||||||
public UInt16 version;
|
public UInt16 version;
|
||||||
@ -41,6 +45,10 @@ namespace TwlBackupBlock
|
|||||||
public UInt32 privateSaveForWPS;
|
public UInt32 privateSaveForWPS;
|
||||||
public readonly Byte[] reservedForWPS;
|
public readonly Byte[] reservedForWPS;
|
||||||
|
|
||||||
|
// for Legacy
|
||||||
|
public UInt32 contentIdForLegacy;
|
||||||
|
public UInt16 contentIndexForLegacy;
|
||||||
|
|
||||||
private BkpType type;
|
private BkpType type;
|
||||||
private int headerSize;
|
private int headerSize;
|
||||||
|
|
||||||
@ -72,12 +80,19 @@ namespace TwlBackupBlock
|
|||||||
|
|
||||||
uniqueId = new Byte[UNIQUE_ID_SIZE];
|
uniqueId = new Byte[UNIQUE_ID_SIZE];
|
||||||
hardwareId = new Byte[HARDWARE_ID_SIZE];
|
hardwareId = new Byte[HARDWARE_ID_SIZE];
|
||||||
fileSizes = new UInt32[NUM_OF_SECTION];
|
if (type != BkpType.LEGACY)
|
||||||
contentId = new UInt32[MAX_CONTENTS];
|
{
|
||||||
contentIndex = new UInt16[MAX_CONTENTS];
|
fileSizes = new UInt32[NUM_OF_SECTION];
|
||||||
|
contentId = new UInt32[MAX_CONTENTS];
|
||||||
|
contentIndex = new UInt16[MAX_CONTENTS];
|
||||||
|
reserved = new Byte[RESERVED_SIZE];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fileSizes = new UInt32[NUM_OF_SECTION_LEGACY];
|
||||||
|
reserved = new Byte[RESERVED_SIZE_FOR_LEGACY];
|
||||||
|
}
|
||||||
tmdReserved = new TmdReserved();
|
tmdReserved = new TmdReserved();
|
||||||
reserved = new Byte[RESERVED_SIZE];
|
|
||||||
|
|
||||||
if (type == BkpType.WITH_PRIVATE_SAVE)
|
if (type == BkpType.WITH_PRIVATE_SAVE)
|
||||||
{
|
{
|
||||||
reservedForWPS = new Byte[RESERVED_SIZE_FOR_WPS];
|
reservedForWPS = new Byte[RESERVED_SIZE_FOR_WPS];
|
||||||
@ -154,7 +169,6 @@ namespace TwlBackupBlock
|
|||||||
using (var ms = new MemoryStream(bytes))
|
using (var ms = new MemoryStream(bytes))
|
||||||
using (var bw = new BinaryWriter(ms))
|
using (var bw = new BinaryWriter(ms))
|
||||||
{
|
{
|
||||||
// TODO : Legacy 対応
|
|
||||||
bw.Write(signature);
|
bw.Write(signature);
|
||||||
bw.Write(companyCode);
|
bw.Write(companyCode);
|
||||||
bw.Write(version);
|
bw.Write(version);
|
||||||
@ -166,16 +180,33 @@ namespace TwlBackupBlock
|
|||||||
{
|
{
|
||||||
bw.Write(e);
|
bw.Write(e);
|
||||||
}
|
}
|
||||||
foreach (UInt32 e in contentId)
|
if (type != BkpType.LEGACY)
|
||||||
{
|
{
|
||||||
bw.Write(e);
|
foreach (UInt32 e in contentId)
|
||||||
|
{
|
||||||
|
bw.Write(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
foreach (UInt16 e in contentIndex)
|
else
|
||||||
{
|
{
|
||||||
bw.Write(e);
|
bw.Write(contentIdForLegacy);
|
||||||
|
}
|
||||||
|
if (type != BkpType.LEGACY)
|
||||||
|
{
|
||||||
|
foreach (UInt16 e in contentIndex)
|
||||||
|
{
|
||||||
|
bw.Write(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bw.Write(tmdReserved.GetBytes());
|
bw.Write(tmdReserved.GetBytes());
|
||||||
bw.Write(headerVersion);
|
if (type == BkpType.LEGACY)
|
||||||
|
{
|
||||||
|
bw.Write(contentIndexForLegacy);
|
||||||
|
}
|
||||||
|
if (type != BkpType.LEGACY)
|
||||||
|
{
|
||||||
|
bw.Write(headerVersion);
|
||||||
|
}
|
||||||
bw.Write(reserved);
|
bw.Write(reserved);
|
||||||
if (type == BkpType.WITH_PRIVATE_SAVE)
|
if (type == BkpType.WITH_PRIVATE_SAVE)
|
||||||
{
|
{
|
||||||
@ -204,7 +235,6 @@ namespace TwlBackupBlock
|
|||||||
using (var ms = new MemoryStream(bytes))
|
using (var ms = new MemoryStream(bytes))
|
||||||
using (var br = new ExtBinaryReader(ms))
|
using (var br = new ExtBinaryReader(ms))
|
||||||
{
|
{
|
||||||
// TODO : Legacy 対応
|
|
||||||
br.Read(ref signature);
|
br.Read(ref signature);
|
||||||
br.Read(ref companyCode);
|
br.Read(ref companyCode);
|
||||||
br.Read(ref version);
|
br.Read(ref version);
|
||||||
@ -213,14 +243,31 @@ namespace TwlBackupBlock
|
|||||||
br.Read(ref titleId);
|
br.Read(ref titleId);
|
||||||
br.Read(ref requiredSize);
|
br.Read(ref requiredSize);
|
||||||
br.Read(fileSizes);
|
br.Read(fileSizes);
|
||||||
br.Read(contentId);
|
if (type != BkpType.LEGACY)
|
||||||
br.Read(contentIndex);
|
{
|
||||||
|
br.Read(contentId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
br.Read(ref contentIdForLegacy);
|
||||||
|
}
|
||||||
|
if (type != BkpType.LEGACY)
|
||||||
|
{
|
||||||
|
br.Read(contentIndex);
|
||||||
|
}
|
||||||
{
|
{
|
||||||
byte[] tmdReservedBytes = new byte[TMD_RESERVED_SIZE];
|
byte[] tmdReservedBytes = new byte[TMD_RESERVED_SIZE];
|
||||||
br.Read(tmdReservedBytes);
|
br.Read(tmdReservedBytes);
|
||||||
tmdReserved.SetBytes(tmdReservedBytes);
|
tmdReserved.SetBytes(tmdReservedBytes);
|
||||||
}
|
}
|
||||||
br.Read(ref headerVersion);
|
if (type == BkpType.LEGACY)
|
||||||
|
{
|
||||||
|
br.Read(ref contentIndexForLegacy);
|
||||||
|
}
|
||||||
|
if (type != BkpType.LEGACY)
|
||||||
|
{
|
||||||
|
br.Read(ref headerVersion);
|
||||||
|
}
|
||||||
br.Read(reserved);
|
br.Read(reserved);
|
||||||
if (type == BkpType.WITH_PRIVATE_SAVE)
|
if (type == BkpType.WITH_PRIVATE_SAVE)
|
||||||
{
|
{
|
||||||
|
@ -23,6 +23,17 @@ namespace TwlBackupBlock
|
|||||||
PRIVATE_SAVE
|
PRIVATE_SAVE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// いまいち・・・
|
||||||
|
public const int MAX_CONTENTS_LEGACY = 1;
|
||||||
|
public enum SectionIndexLegacy
|
||||||
|
{
|
||||||
|
TMD,
|
||||||
|
CONTENT,
|
||||||
|
PUBLIC_SAVE = CONTENT + MAX_CONTENTS_LEGACY,
|
||||||
|
SUB_BANNER,
|
||||||
|
PRIVATE_SAVE
|
||||||
|
};
|
||||||
|
|
||||||
private const int KEY_SIZE = 16;
|
private const int KEY_SIZE = 16;
|
||||||
private const int ROUND_SCALE = 16;
|
private const int ROUND_SCALE = 16;
|
||||||
|
|
||||||
@ -41,8 +52,10 @@ namespace TwlBackupBlock
|
|||||||
private const int ENCRYPT_BANNER_SIZE = BANNER_SIZE + AES_SIGN_HEADER_SIZE;
|
private const int ENCRYPT_BANNER_SIZE = BANNER_SIZE + AES_SIGN_HEADER_SIZE;
|
||||||
private const int ENCRYPT_HEADER_SIZE = HEADER_SIZE + AES_SIGN_HEADER_SIZE;
|
private const int ENCRYPT_HEADER_SIZE = HEADER_SIZE + AES_SIGN_HEADER_SIZE;
|
||||||
private const int ENCRYPT_EX_HEADER_SIZE = EX_HEADER_SIZE + AES_SIGN_HEADER_SIZE;
|
private const int ENCRYPT_EX_HEADER_SIZE = EX_HEADER_SIZE + AES_SIGN_HEADER_SIZE;
|
||||||
|
private const int ENCRYPT_LEGACY_HEADER_SIZE = LEGACY_HEADER_SIZE + AES_SIGN_HEADER_SIZE;
|
||||||
private const int ENCRYPT_SIGNATURE_SIZE = SIGNATURE_SIZE + AES_SIGN_HEADER_SIZE;
|
private const int ENCRYPT_SIGNATURE_SIZE = SIGNATURE_SIZE + AES_SIGN_HEADER_SIZE;
|
||||||
private const int ENCRYPT_EX_SIGNATURE_SIZE = EX_SIGNATURE_SIZE + AES_SIGN_HEADER_SIZE;
|
private const int ENCRYPT_EX_SIGNATURE_SIZE = EX_SIGNATURE_SIZE + AES_SIGN_HEADER_SIZE;
|
||||||
|
private const int ENCRYPT_LEGACY_SIGNATURE_SIZE = LEGACY_SIGNATURE_SIZE + AES_SIGN_HEADER_SIZE;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// バックアップデータをブロックに分割して復号化をおこないます。
|
/// バックアップデータをブロックに分割して復号化をおこないます。
|
||||||
@ -53,17 +66,13 @@ namespace TwlBackupBlock
|
|||||||
/// <returns>分割、復号化されたブロック。</returns>
|
/// <returns>分割、復号化されたブロック。</returns>
|
||||||
public static Blocks DecryptBackupData(byte[] data, byte[] key, BkpType type)
|
public static Blocks DecryptBackupData(byte[] data, byte[] key, BkpType type)
|
||||||
{
|
{
|
||||||
// TODO : Legacy 対応
|
|
||||||
if (type == BkpType.LEGACY)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Legacy には未対応です。");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int headerSize = 0;
|
int headerSize = 0;
|
||||||
int signatureSize = 0;
|
int signatureSize = 0;
|
||||||
int encryptHeaderSize = 0;
|
int encryptHeaderSize = 0;
|
||||||
int encryptSignatureSize = 0;
|
int encryptSignatureSize = 0;
|
||||||
|
int contentsNum = MAX_CONTENTS;
|
||||||
|
int indexPublicSave = (int)SectionIndexLegacy.PUBLIC_SAVE;
|
||||||
|
int indexSubBanner = (int)SectionIndexLegacy.SUB_BANNER;
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
@ -80,6 +89,16 @@ namespace TwlBackupBlock
|
|||||||
encryptHeaderSize = ENCRYPT_EX_HEADER_SIZE;
|
encryptHeaderSize = ENCRYPT_EX_HEADER_SIZE;
|
||||||
encryptSignatureSize = ENCRYPT_EX_SIGNATURE_SIZE;
|
encryptSignatureSize = ENCRYPT_EX_SIGNATURE_SIZE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case BkpType.LEGACY:
|
||||||
|
headerSize = LEGACY_HEADER_SIZE;
|
||||||
|
signatureSize = LEGACY_SIGNATURE_SIZE;
|
||||||
|
encryptHeaderSize = ENCRYPT_LEGACY_HEADER_SIZE;
|
||||||
|
encryptSignatureSize = ENCRYPT_LEGACY_SIGNATURE_SIZE;
|
||||||
|
contentsNum = MAX_CONTENTS_LEGACY;
|
||||||
|
indexPublicSave = (int)SectionIndexLegacy.PUBLIC_SAVE;
|
||||||
|
indexSubBanner = (int)SectionIndexLegacy.SUB_BANNER;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data == null)
|
if (data == null)
|
||||||
@ -122,15 +141,14 @@ namespace TwlBackupBlock
|
|||||||
blocks.signature.body = new SignatureBody(
|
blocks.signature.body = new SignatureBody(
|
||||||
DecryptBlock(br, signatureSize, key, out blocks.signature.mac, out blocks.signature.iv), type);
|
DecryptBlock(br, signatureSize, key, out blocks.signature.mac, out blocks.signature.iv), type);
|
||||||
|
|
||||||
// TODO : Legacy 対応
|
|
||||||
int TMD_SIZE = (int)headerBody.fileSizes[(int)SectionIndex.TMD];
|
int TMD_SIZE = (int)headerBody.fileSizes[(int)SectionIndex.TMD];
|
||||||
int[] CONTENT_SIZE = new int[MAX_CONTENTS];
|
int[] CONTENT_SIZE = new int[contentsNum];
|
||||||
for (int i = 0; i < MAX_CONTENTS; i++)
|
for (int i = 0; i < contentsNum; i++)
|
||||||
{
|
{
|
||||||
CONTENT_SIZE[i] = (int)headerBody.fileSizes[(int)SectionIndex.CONTENT + i];
|
CONTENT_SIZE[i] = (int)headerBody.fileSizes[(int)SectionIndex.CONTENT + i];
|
||||||
}
|
}
|
||||||
int PUBLIC_SAVE_SIZE = (int)headerBody.fileSizes[(int)SectionIndex.PUBLIC_SAVE];
|
int PUBLIC_SAVE_SIZE = (int)headerBody.fileSizes[indexPublicSave];
|
||||||
int SUB_BANNER_SIZE = (int)headerBody.fileSizes[(int)SectionIndex.SUB_BANNER];
|
int SUB_BANNER_SIZE = (int)headerBody.fileSizes[indexSubBanner];
|
||||||
int PRIVATE_SAVE_SIZE = 0;
|
int PRIVATE_SAVE_SIZE = 0;
|
||||||
if (type == BkpType.WITH_PRIVATE_SAVE)
|
if (type == BkpType.WITH_PRIVATE_SAVE)
|
||||||
{
|
{
|
||||||
@ -138,7 +156,7 @@ namespace TwlBackupBlock
|
|||||||
}
|
}
|
||||||
|
|
||||||
blocks.tmd.body = new Body(DecryptBlock(br, TMD_SIZE, key, out blocks.tmd.mac, out blocks.tmd.iv));
|
blocks.tmd.body = new Body(DecryptBlock(br, TMD_SIZE, key, out blocks.tmd.mac, out blocks.tmd.iv));
|
||||||
for (int i = 0; i < MAX_CONTENTS; i++)
|
for (int i = 0; i < contentsNum; i++)
|
||||||
{
|
{
|
||||||
if (CONTENT_SIZE[i] > 0)
|
if (CONTENT_SIZE[i] > 0)
|
||||||
{
|
{
|
||||||
@ -393,17 +411,13 @@ namespace TwlBackupBlock
|
|||||||
/// <returns>検証に成功した場合はtrue。それ以外の場合はfalse。</returns>
|
/// <returns>検証に成功した場合はtrue。それ以外の場合はfalse。</returns>
|
||||||
public static bool Verify(byte[] data, byte[] aesCbcKey, byte[] aesCmacKey, BkpType type)
|
public static bool Verify(byte[] data, byte[] aesCbcKey, byte[] aesCmacKey, BkpType type)
|
||||||
{
|
{
|
||||||
// TODO : Legacy 対応
|
|
||||||
if (type == BkpType.LEGACY)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Legacy には未対応です。");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int headerSize = 0;
|
int headerSize = 0;
|
||||||
int signatureSize = 0;
|
int signatureSize = 0;
|
||||||
int encryptHeaderSize = 0;
|
int encryptHeaderSize = 0;
|
||||||
int encryptSignatureSize = 0;
|
int encryptSignatureSize = 0;
|
||||||
|
int contentsNum = MAX_CONTENTS;
|
||||||
|
int indexPublicSave = (int)SectionIndex.PUBLIC_SAVE;
|
||||||
|
int indexSubBanner = (int)SectionIndex.SUB_BANNER;
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
@ -420,6 +434,16 @@ namespace TwlBackupBlock
|
|||||||
encryptHeaderSize = ENCRYPT_EX_HEADER_SIZE;
|
encryptHeaderSize = ENCRYPT_EX_HEADER_SIZE;
|
||||||
encryptSignatureSize = ENCRYPT_EX_SIGNATURE_SIZE;
|
encryptSignatureSize = ENCRYPT_EX_SIGNATURE_SIZE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case BkpType.LEGACY:
|
||||||
|
headerSize = LEGACY_HEADER_SIZE;
|
||||||
|
signatureSize = LEGACY_SIGNATURE_SIZE;
|
||||||
|
encryptHeaderSize = ENCRYPT_LEGACY_HEADER_SIZE;
|
||||||
|
encryptSignatureSize = ENCRYPT_LEGACY_SIGNATURE_SIZE;
|
||||||
|
contentsNum = MAX_CONTENTS_LEGACY;
|
||||||
|
indexPublicSave = (int)SectionIndexLegacy.PUBLIC_SAVE;
|
||||||
|
indexSubBanner = (int)SectionIndexLegacy.SUB_BANNER;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data == null)
|
if (data == null)
|
||||||
@ -492,13 +516,13 @@ namespace TwlBackupBlock
|
|||||||
|
|
||||||
// TODO : Legacy 対応
|
// TODO : Legacy 対応
|
||||||
int TMD_SIZE = (int)headerBody.fileSizes[(int)SectionIndex.TMD];
|
int TMD_SIZE = (int)headerBody.fileSizes[(int)SectionIndex.TMD];
|
||||||
int[] CONTENT_SIZE = new int[MAX_CONTENTS];
|
int[] CONTENT_SIZE = new int[contentsNum];
|
||||||
for (int i = 0; i < MAX_CONTENTS; i++)
|
for (int i = 0; i < contentsNum; i++)
|
||||||
{
|
{
|
||||||
CONTENT_SIZE[i] = (int)headerBody.fileSizes[(int)SectionIndex.CONTENT + i];
|
CONTENT_SIZE[i] = (int)headerBody.fileSizes[(int)SectionIndex.CONTENT + i];
|
||||||
}
|
}
|
||||||
int PUBLIC_SAVE_SIZE = (int)headerBody.fileSizes[(int)SectionIndex.PUBLIC_SAVE];
|
int PUBLIC_SAVE_SIZE = (int)headerBody.fileSizes[indexPublicSave];
|
||||||
int SUB_BANNER_SIZE = (int)headerBody.fileSizes[(int)SectionIndex.SUB_BANNER];
|
int SUB_BANNER_SIZE = (int)headerBody.fileSizes[indexSubBanner];
|
||||||
int PRIVATE_SAVE_SIZE = 0;
|
int PRIVATE_SAVE_SIZE = 0;
|
||||||
if (type == BkpType.WITH_PRIVATE_SAVE)
|
if (type == BkpType.WITH_PRIVATE_SAVE)
|
||||||
{
|
{
|
||||||
@ -508,7 +532,7 @@ namespace TwlBackupBlock
|
|||||||
int restSize = RoundUp(TMD_SIZE, ROUND_SCALE) +
|
int restSize = RoundUp(TMD_SIZE, ROUND_SCALE) +
|
||||||
RoundUp(PUBLIC_SAVE_SIZE, ROUND_SCALE) + RoundUp(SUB_BANNER_SIZE, ROUND_SCALE) +
|
RoundUp(PUBLIC_SAVE_SIZE, ROUND_SCALE) + RoundUp(SUB_BANNER_SIZE, ROUND_SCALE) +
|
||||||
AES_SIGN_HEADER_SIZE * 3;
|
AES_SIGN_HEADER_SIZE * 3;
|
||||||
for (int i = 0; i < MAX_CONTENTS; i++)
|
for (int i = 0; i < contentsNum; i++)
|
||||||
{
|
{
|
||||||
if (CONTENT_SIZE[i] > 0)
|
if (CONTENT_SIZE[i] > 0)
|
||||||
{
|
{
|
||||||
@ -536,7 +560,7 @@ namespace TwlBackupBlock
|
|||||||
Console.WriteLine("TMDブロックのハッシュが一致しません");
|
Console.WriteLine("TMDブロックのハッシュが一致しません");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < MAX_CONTENTS; i++)
|
for (int i = 0; i < contentsNum; i++)
|
||||||
{
|
{
|
||||||
if (CONTENT_SIZE[i] > 0)
|
if (CONTENT_SIZE[i] > 0)
|
||||||
{
|
{
|
||||||
@ -560,7 +584,7 @@ namespace TwlBackupBlock
|
|||||||
Console.WriteLine("publicセーブデータブロックのMACが一致しません");
|
Console.WriteLine("publicセーブデータブロックのMACが一致しません");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!CheckHash(blocks.publicSave, signatureBody.digest.section[(int)SectionIndex.PUBLIC_SAVE]))
|
if (!CheckHash(blocks.publicSave, signatureBody.digest.section[indexPublicSave]))
|
||||||
{
|
{
|
||||||
Console.WriteLine("publicセーブデータブロックのハッシュが一致しません");
|
Console.WriteLine("publicセーブデータブロックのハッシュが一致しません");
|
||||||
return false;
|
return false;
|
||||||
@ -571,7 +595,7 @@ namespace TwlBackupBlock
|
|||||||
Console.WriteLine("サブバナーブロックの内容とMACが一致しません");
|
Console.WriteLine("サブバナーブロックの内容とMACが一致しません");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!CheckHash(blocks.subBanner, signatureBody.digest.section[(int)SectionIndex.SUB_BANNER]))
|
if (!CheckHash(blocks.subBanner, signatureBody.digest.section[indexSubBanner]))
|
||||||
{
|
{
|
||||||
Console.WriteLine("サブバナーブロックのハッシュが一致しません");
|
Console.WriteLine("サブバナーブロックのハッシュが一致しません");
|
||||||
return false;
|
return false;
|
||||||
|
Loading…
Reference in New Issue
Block a user