mirror of
https://github.com/Xevion/easy7zip.git
synced 2025-12-14 20:11:38 -06:00
4.20
This commit is contained in:
committed by
Kornel Lesiński
parent
8c1b5c7b7e
commit
3c510ba80b
@@ -1,60 +1,42 @@
|
||||
// Compress/HuffmanDecoder.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __COMPRESS_HUFFMANDECODER_H
|
||||
#define __COMPRESS_HUFFMANDECODER_H
|
||||
|
||||
#include "../../../Common/Types.h"
|
||||
|
||||
namespace NCompress {
|
||||
namespace NHuffman {
|
||||
|
||||
class CDecoderException{};
|
||||
|
||||
const UINT32 kValueTableBits = 8;
|
||||
const UInt32 kValueTableBits = 9;
|
||||
|
||||
template <int kNumBitsInLongestCode>
|
||||
template <int kNumBitsInLongestCode, UInt32 m_NumSymbols>
|
||||
class CDecoder
|
||||
{
|
||||
UINT32 m_Limitits[kNumBitsInLongestCode + 1]; // m_Limitits[i] = value limit for symbols with length = i
|
||||
UINT32 m_Positions[kNumBitsInLongestCode + 1]; // m_Positions[i] = index in m_Symbols[] of first symbol with length = i
|
||||
UINT32 m_NumSymbols;
|
||||
UINT32 *m_Symbols; // symbols: at first with len = 1 then 2, ... 15.
|
||||
BYTE m_Lengths[1 << kValueTableBits];
|
||||
UInt32 m_Limitits[kNumBitsInLongestCode + 1]; // m_Limitits[i] = value limit for symbols with length = i
|
||||
UInt32 m_Positions[kNumBitsInLongestCode + 1]; // m_Positions[i] = index in m_Symbols[] of first symbol with length = i
|
||||
UInt32 m_Symbols[m_NumSymbols]; // symbols: at first with len = 1 then 2, ... 15.
|
||||
Byte m_Lengths[1 << kValueTableBits];
|
||||
public:
|
||||
CDecoder(UINT32 numSymbols):
|
||||
m_NumSymbols(numSymbols)
|
||||
{ m_Symbols = new UINT32[m_NumSymbols]; }
|
||||
|
||||
~CDecoder()
|
||||
{ delete []m_Symbols; }
|
||||
|
||||
void SetNumSymbols(UINT32 numSymbols)
|
||||
{ m_NumSymbols = numSymbols; }
|
||||
void SetCodeLengths(const BYTE *codeLengths);
|
||||
void SetNumSymbols(UInt32 numSymbols) { m_NumSymbols = numSymbols; }
|
||||
void SetCodeLengths(const Byte *codeLengths);
|
||||
template <class TBitDecoder>
|
||||
UINT32 DecodeSymbol(TBitDecoder *bitStream)
|
||||
UInt32 DecodeSymbol(TBitDecoder *bitStream)
|
||||
{
|
||||
UINT32 numBits;
|
||||
UInt32 numBits;
|
||||
|
||||
UINT32 value = bitStream->GetValue(kNumBitsInLongestCode);
|
||||
UInt32 value = bitStream->GetValue(kNumBitsInLongestCode);
|
||||
|
||||
if (value < m_Limitits[kValueTableBits])
|
||||
numBits = m_Lengths[value >> (kNumBitsInLongestCode - kValueTableBits)];
|
||||
else if (value < m_Limitits[10])
|
||||
if (value < m_Limitits[9])
|
||||
numBits = 9;
|
||||
else
|
||||
numBits = 10;
|
||||
else if (value < m_Limitits[11])
|
||||
numBits = 11;
|
||||
else if (value < m_Limitits[12])
|
||||
numBits = 12;
|
||||
else
|
||||
for (numBits = 13; numBits < kNumBitsInLongestCode; numBits++)
|
||||
for (numBits = kValueTableBits + 1; numBits < kNumBitsInLongestCode; numBits++)
|
||||
if (value < m_Limitits[numBits])
|
||||
break;
|
||||
bitStream->MovePos(numBits);
|
||||
UINT32 index = m_Positions[numBits] +
|
||||
UInt32 index = m_Positions[numBits] +
|
||||
((value - m_Limitits[numBits - 1]) >> (kNumBitsInLongestCode - numBits));
|
||||
if (index >= m_NumSymbols)
|
||||
throw CDecoderException(); // test it
|
||||
@@ -62,27 +44,26 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <int kNumBitsInLongestCode>
|
||||
void CDecoder<kNumBitsInLongestCode>::SetCodeLengths(const BYTE *codeLengths)
|
||||
template <int kNumBitsInLongestCode, UInt32 m_NumSymbols>
|
||||
void CDecoder<kNumBitsInLongestCode, m_NumSymbols>::SetCodeLengths(const Byte *codeLengths)
|
||||
{
|
||||
int lenCounts[kNumBitsInLongestCode + 1], tmpPositions[kNumBitsInLongestCode + 1];
|
||||
int i;
|
||||
for(i = 1; i <= kNumBitsInLongestCode; i++)
|
||||
lenCounts[i] = 0;
|
||||
UINT32 symbol;
|
||||
UInt32 symbol;
|
||||
for (symbol = 0; symbol < m_NumSymbols; symbol++)
|
||||
{
|
||||
BYTE codeLength = codeLengths[symbol];
|
||||
Byte codeLength = codeLengths[symbol];
|
||||
if (codeLength > kNumBitsInLongestCode)
|
||||
throw CDecoderException();
|
||||
lenCounts[codeLength]++;
|
||||
}
|
||||
lenCounts[0] = 0;
|
||||
tmpPositions[0] = m_Positions[0] = m_Limitits[0] = 0;
|
||||
UINT32 startPos = 0;
|
||||
UINT32 index = 0;
|
||||
const UINT32 kMaxValue = (1 << kNumBitsInLongestCode);
|
||||
UInt32 startPos = 0;
|
||||
UInt32 index = 0;
|
||||
const UInt32 kMaxValue = (1 << kNumBitsInLongestCode);
|
||||
for (i = 1; i <= kNumBitsInLongestCode; i++)
|
||||
{
|
||||
startPos += lenCounts[i] << (kNumBitsInLongestCode - i);
|
||||
@@ -94,8 +75,8 @@ void CDecoder<kNumBitsInLongestCode>::SetCodeLengths(const BYTE *codeLengths)
|
||||
|
||||
if(i <= kValueTableBits)
|
||||
{
|
||||
UINT32 limit = (m_Limitits[i] >> (kNumBitsInLongestCode - kValueTableBits)); // change it
|
||||
memset(m_Lengths + index, BYTE(i), limit - index);
|
||||
UInt32 limit = (m_Limitits[i] >> (kNumBitsInLongestCode - kValueTableBits)); // change it
|
||||
memset(m_Lengths + index, Byte(i), limit - index);
|
||||
index = limit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,41 +4,63 @@
|
||||
|
||||
#include "HuffmanEncoder.h"
|
||||
#include "Common/Defs.h"
|
||||
#include "Common/Alloc.h"
|
||||
|
||||
namespace NCompression {
|
||||
namespace NHuffman {
|
||||
|
||||
static const char *kIncorrectBitLenCountsMessage = "Incorrect bit len counts";
|
||||
|
||||
CEncoder::CEncoder(UINT32 numSymbols,
|
||||
const BYTE *extraBits, UINT32 extraBase, UINT32 maxLength):
|
||||
m_NumSymbols(numSymbols),
|
||||
m_ExtraBits(extraBits),
|
||||
m_ExtraBase(extraBase),
|
||||
m_MaxLength(maxLength),
|
||||
m_HeapSize(numSymbols * 2+ 1)
|
||||
CEncoder::CEncoder():
|
||||
m_Items(0),
|
||||
m_Heap(0),
|
||||
m_Depth(0)
|
||||
{}
|
||||
|
||||
void CEncoder::Free()
|
||||
{
|
||||
m_Items = new CItem[m_HeapSize];
|
||||
m_Heap = new UINT32[m_HeapSize];
|
||||
m_Depth = new BYTE[m_HeapSize];
|
||||
MyFree(m_Items);
|
||||
MyFree(m_Heap);
|
||||
MyFree(m_Depth);
|
||||
m_Items = 0;
|
||||
m_Heap = 0;
|
||||
m_Depth = 0;
|
||||
}
|
||||
|
||||
bool CEncoder::Create(UInt32 numSymbols,
|
||||
const Byte *extraBits, UInt32 extraBase, UInt32 maxLength)
|
||||
{
|
||||
m_NumSymbols = numSymbols;
|
||||
m_ExtraBits = extraBits;
|
||||
m_ExtraBase = extraBase;
|
||||
m_MaxLength = maxLength;
|
||||
m_HeapSize = numSymbols * 2 + 1;
|
||||
Free();
|
||||
m_Items = (CItem *)MyAlloc(m_HeapSize * sizeof(CItem));
|
||||
m_Heap = (UInt32 *)MyAlloc(m_HeapSize * sizeof(UInt32));
|
||||
m_Depth = (Byte *)MyAlloc(m_HeapSize * sizeof(Byte));
|
||||
if (m_Items == 0 || m_Heap == 0 || m_Depth == 0)
|
||||
{
|
||||
Free();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
CEncoder::~CEncoder()
|
||||
{
|
||||
delete []m_Depth;
|
||||
delete []m_Heap;
|
||||
delete []m_Items;
|
||||
Free();
|
||||
}
|
||||
|
||||
void CEncoder::StartNewBlock()
|
||||
{
|
||||
for (UINT32 i = 0; i < m_NumSymbols; i++)
|
||||
for (UInt32 i = 0; i < m_NumSymbols; i++)
|
||||
m_Items[i].Freq = 0;
|
||||
}
|
||||
|
||||
void CEncoder::SetFreqs(const UINT32 *freqs)
|
||||
void CEncoder::SetFreqs(const UInt32 *freqs)
|
||||
{
|
||||
for (UINT32 i = 0; i < m_NumSymbols; i++)
|
||||
for (UInt32 i = 0; i < m_NumSymbols; i++)
|
||||
m_Items[i].Freq = freqs[i];
|
||||
}
|
||||
|
||||
@@ -48,9 +70,9 @@ static const int kSmallest = 1;
|
||||
// Remove the smallest element from the heap and recreate the heap with
|
||||
// one less element. Updates heap and m_HeapLength.
|
||||
|
||||
UINT32 CEncoder::RemoveSmallest()
|
||||
UInt32 CEncoder::RemoveSmallest()
|
||||
{
|
||||
UINT32 top = m_Heap[kSmallest];
|
||||
UInt32 top = m_Heap[kSmallest];
|
||||
m_Heap[kSmallest] = m_Heap[m_HeapLength--];
|
||||
DownHeap(kSmallest);
|
||||
return top;
|
||||
@@ -72,15 +94,15 @@ bool CEncoder::Smaller(int n, int m)
|
||||
// when the m_Heap property is re-established (each father CompareFreqs than its
|
||||
// two sons).
|
||||
|
||||
void CEncoder::DownHeap(UINT32 k)
|
||||
void CEncoder::DownHeap(UInt32 k)
|
||||
{
|
||||
UINT32 symbol = m_Heap[k];
|
||||
for (UINT32 j = k << 1; j <= m_HeapLength;) // j: left son of k
|
||||
UInt32 symbol = m_Heap[k];
|
||||
for (UInt32 j = k << 1; j <= m_HeapLength;) // j: left son of k
|
||||
{
|
||||
// Set j to the smallest of the two sons:
|
||||
if (j < m_HeapLength && Smaller(m_Heap[j+1], m_Heap[j]))
|
||||
j++;
|
||||
UINT32 htemp = m_Heap[j]; // htemp required because of bug in SASC compiler
|
||||
UInt32 htemp = m_Heap[j]; // htemp required because of bug in SASC compiler
|
||||
if (Smaller(symbol, htemp)) // Exit if v is smaller than both sons
|
||||
break;
|
||||
m_Heap[k] = htemp; // Exchange v with the smallest son
|
||||
@@ -100,22 +122,22 @@ void CEncoder::DownHeap(UINT32 k)
|
||||
// The length m_BlockBitLength is updated; static_len is also updated if stree is
|
||||
// not null.
|
||||
|
||||
void CEncoder::GenerateBitLen(UINT32 maxCode, UINT32 heapMax)
|
||||
void CEncoder::GenerateBitLen(UInt32 maxCode, UInt32 heapMax)
|
||||
{
|
||||
int overflow = 0; // number of elements with bit length too large
|
||||
|
||||
for (UINT32 i = 0; i <= kNumBitsInLongestCode; i++)
|
||||
for (UInt32 i = 0; i <= kNumBitsInLongestCode; i++)
|
||||
m_BitLenCounters[i] = 0;
|
||||
|
||||
/* In a first pass, compute the optimal bit lengths (which may
|
||||
* overflow in the case of the bit length tree).
|
||||
*/
|
||||
m_Items[m_Heap[heapMax]].Len = 0; /* root of the heap */
|
||||
UINT32 h; /* heap index */
|
||||
UInt32 h; /* heap index */
|
||||
for (h = heapMax+1; h < m_HeapSize; h++)
|
||||
{
|
||||
UINT32 symbol = m_Heap[h];
|
||||
UINT32 len = m_Items[m_Items[symbol].Dad].Len + 1;
|
||||
UInt32 symbol = m_Heap[h];
|
||||
UInt32 len = m_Items[m_Items[symbol].Dad].Len + 1;
|
||||
if (len > m_MaxLength)
|
||||
{
|
||||
len = m_MaxLength;
|
||||
@@ -125,7 +147,7 @@ void CEncoder::GenerateBitLen(UINT32 maxCode, UINT32 heapMax)
|
||||
if (symbol > maxCode)
|
||||
continue; // not a leaf node
|
||||
m_BitLenCounters[len]++;
|
||||
UINT32 extraBits;
|
||||
UInt32 extraBits;
|
||||
if (m_ExtraBits != 0 && symbol >= m_ExtraBase)
|
||||
extraBits = m_ExtraBits[symbol - m_ExtraBase];
|
||||
else
|
||||
@@ -139,7 +161,7 @@ void CEncoder::GenerateBitLen(UINT32 maxCode, UINT32 heapMax)
|
||||
// Find the first bit length which could increase:
|
||||
do
|
||||
{
|
||||
UINT32 bits = m_MaxLength-1;
|
||||
UInt32 bits = m_MaxLength-1;
|
||||
while (m_BitLenCounters[bits] == 0)
|
||||
bits--;
|
||||
m_BitLenCounters[bits]--; // move one leaf down the m_Items
|
||||
@@ -155,12 +177,12 @@ void CEncoder::GenerateBitLen(UINT32 maxCode, UINT32 heapMax)
|
||||
// h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
|
||||
// lengths instead of fixing only the wrong ones. This idea is taken
|
||||
// from 'ar' written by Haruhiko Okumura.)
|
||||
for (UINT32 bits = m_MaxLength; bits != 0; bits--)
|
||||
for (UInt32 bits = m_MaxLength; bits != 0; bits--)
|
||||
{
|
||||
UINT32 numNodes = m_BitLenCounters[bits];
|
||||
UInt32 numNodes = m_BitLenCounters[bits];
|
||||
while (numNodes != 0)
|
||||
{
|
||||
UINT32 m = m_Heap[--h];
|
||||
UInt32 m = m_Heap[--h];
|
||||
if (m > maxCode)
|
||||
continue;
|
||||
if (m_Items[m].Len != (unsigned) bits)
|
||||
@@ -182,22 +204,22 @@ void CEncoder::GenerateBitLen(UINT32 maxCode, UINT32 heapMax)
|
||||
// OUT assertion: the field code is set for all tree elements of non
|
||||
// zero code length.
|
||||
|
||||
// UINT32 maxCode = largest code with non zero frequency
|
||||
// UInt32 maxCode = largest code with non zero frequency
|
||||
|
||||
|
||||
void CEncoder::GenerateCodes(UINT32 maxCode)
|
||||
void CEncoder::GenerateCodes(UInt32 maxCode)
|
||||
{
|
||||
UINT32 nextCodes[kNumBitsInLongestCode + 1]; // next code value for each bit length
|
||||
UINT32 code = 0; // running code value
|
||||
UInt32 nextCodes[kNumBitsInLongestCode + 1]; // next code value for each bit length
|
||||
UInt32 code = 0; // running code value
|
||||
// The distribution counts are first used to generate the code values
|
||||
// without bit reversal.
|
||||
for (UINT32 bits = 1; bits <= kNumBitsInLongestCode; bits++)
|
||||
for (UInt32 bits = 1; bits <= kNumBitsInLongestCode; bits++)
|
||||
nextCodes[bits] = code = (code + m_BitLenCounters[bits - 1]) << 1;
|
||||
// Check that the bit counts in m_BitLenCounters are consistent. The last code
|
||||
// must be all ones.
|
||||
if (code + m_BitLenCounters[kNumBitsInLongestCode] - 1 != (1 << kNumBitsInLongestCode) - 1)
|
||||
throw kIncorrectBitLenCountsMessage;
|
||||
for (UINT32 n = 0; n <= maxCode; n++)
|
||||
for (UInt32 n = 0; n <= maxCode; n++)
|
||||
{
|
||||
int len = m_Items[n].Len;
|
||||
if (len == 0)
|
||||
@@ -215,7 +237,7 @@ void CEncoder::GenerateCodes(UINT32 maxCode)
|
||||
// and corresponding code. The length m_BlockBitLength is updated; static_len is
|
||||
// also updated if stree is not null. The field max_code is set.
|
||||
|
||||
void CEncoder::BuildTree(BYTE *levels)
|
||||
void CEncoder::BuildTree(Byte *levels)
|
||||
{
|
||||
m_BlockBitLength = 0;
|
||||
int maxCode = -1; // WAS = -1; largest code with non zero frequency */
|
||||
@@ -226,7 +248,7 @@ void CEncoder::BuildTree(BYTE *levels)
|
||||
//
|
||||
|
||||
m_HeapLength = 0;
|
||||
UINT32 n; // iterate over m_Heap elements
|
||||
UInt32 n; // iterate over m_Heap elements
|
||||
for (n = 0; n < m_NumSymbols; n++)
|
||||
{
|
||||
if (m_Items[n].Freq != 0)
|
||||
@@ -260,18 +282,18 @@ void CEncoder::BuildTree(BYTE *levels)
|
||||
// Construct the Huffman tree by repeatedly combining the least two
|
||||
// frequent nodes.
|
||||
int node = m_NumSymbols; // next internal node of the tree
|
||||
UINT32 heapMax = m_NumSymbols * 2+ 1;
|
||||
UInt32 heapMax = m_NumSymbols * 2+ 1;
|
||||
do
|
||||
{
|
||||
n = RemoveSmallest(); /* n = node of least frequency */
|
||||
UINT32 m = m_Heap[kSmallest]; /* m = node of next least frequency */
|
||||
UInt32 m = m_Heap[kSmallest]; /* m = node of next least frequency */
|
||||
|
||||
m_Heap[--heapMax] = n; /* keep the nodes sorted by frequency */
|
||||
m_Heap[--heapMax] = m;
|
||||
|
||||
// Create a new node father of n and m
|
||||
m_Items[node].Freq = m_Items[n].Freq + m_Items[m].Freq;
|
||||
m_Depth[node] = (BYTE) (MyMax(m_Depth[n], m_Depth[m]) + 1);
|
||||
m_Depth[node] = (Byte) (MyMax(m_Depth[n], m_Depth[m]) + 1);
|
||||
m_Items[n].Dad = m_Items[m].Dad = node;
|
||||
// and insert the new node in the m_Heap
|
||||
m_Heap[kSmallest] = node++;
|
||||
@@ -290,7 +312,7 @@ void CEncoder::BuildTree(BYTE *levels)
|
||||
GenerateCodes (maxCode);
|
||||
|
||||
for (n = 0; n < m_NumSymbols; n++)
|
||||
levels[n] = BYTE(m_Items[n].Len);
|
||||
levels[n] = Byte(m_Items[n].Len);
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// Compression/HuffmanEncoder.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __COMPRESSION_HUFFMANENCODER_H
|
||||
#define __COMPRESSION_HUFFMANENCODER_H
|
||||
|
||||
@@ -10,54 +8,58 @@
|
||||
namespace NCompression {
|
||||
namespace NHuffman {
|
||||
|
||||
const int kNumBitsInLongestCode = 15;
|
||||
const int kNumBitsInLongestCode = 20;
|
||||
|
||||
struct CItem
|
||||
{
|
||||
UINT32 Freq;
|
||||
UINT32 Code;
|
||||
UINT32 Dad;
|
||||
UINT32 Len;
|
||||
UInt32 Freq;
|
||||
UInt32 Code;
|
||||
UInt32 Dad;
|
||||
UInt32 Len;
|
||||
};
|
||||
|
||||
class CEncoder
|
||||
{
|
||||
UINT32 m_NumSymbols; // number of symbols in adwSymbol
|
||||
public:
|
||||
UInt32 m_NumSymbols; // number of symbols in adwSymbol
|
||||
|
||||
CItem *m_Items;
|
||||
UINT32 *m_Heap;
|
||||
UINT32 m_HeapSize;
|
||||
BYTE *m_Depth;
|
||||
const BYTE *m_ExtraBits;
|
||||
UINT32 m_ExtraBase;
|
||||
UINT32 m_MaxLength;
|
||||
UInt32 *m_Heap;
|
||||
UInt32 m_HeapSize;
|
||||
Byte *m_Depth;
|
||||
const Byte *m_ExtraBits;
|
||||
UInt32 m_ExtraBase;
|
||||
UInt32 m_MaxLength;
|
||||
|
||||
UINT32 m_HeapLength;
|
||||
UINT32 m_BitLenCounters[kNumBitsInLongestCode + 1];
|
||||
UInt32 m_HeapLength;
|
||||
UInt32 m_BitLenCounters[kNumBitsInLongestCode + 1];
|
||||
|
||||
UINT32 RemoveSmallest();
|
||||
UInt32 RemoveSmallest();
|
||||
bool Smaller(int n, int m);
|
||||
void DownHeap(UINT32 k);
|
||||
void GenerateBitLen(UINT32 maxCode, UINT32 heapMax);
|
||||
void GenerateCodes(UINT32 maxCode);
|
||||
void DownHeap(UInt32 k);
|
||||
void GenerateBitLen(UInt32 maxCode, UInt32 heapMax);
|
||||
void GenerateCodes(UInt32 maxCode);
|
||||
|
||||
UINT32 m_BlockBitLength;
|
||||
UInt32 m_BlockBitLength;
|
||||
|
||||
void Free();
|
||||
|
||||
public:
|
||||
|
||||
CEncoder(UINT32 numSymbols, const BYTE *extraBits,
|
||||
UINT32 extraBase, UINT32 maxLength);
|
||||
CEncoder();
|
||||
~CEncoder();
|
||||
bool Create(UInt32 numSymbols, const Byte *extraBits,
|
||||
UInt32 extraBase, UInt32 maxLength);
|
||||
void StartNewBlock();
|
||||
|
||||
void AddSymbol(UINT32 symbol)
|
||||
{ m_Items[symbol].Freq++; }
|
||||
void AddSymbol(UInt32 symbol) { m_Items[symbol].Freq++; }
|
||||
|
||||
void SetFreqs(const UINT32 *freqs);
|
||||
void BuildTree(BYTE *levels);
|
||||
UINT32 GetBlockBitLength() const { return m_BlockBitLength; }
|
||||
void SetFreqs(const UInt32 *freqs);
|
||||
void BuildTree(Byte *levels);
|
||||
UInt32 GetBlockBitLength() const { return m_BlockBitLength; }
|
||||
|
||||
template <class TBitEncoder>
|
||||
void CodeOneValue(TBitEncoder *bitEncoder, UINT32 symbol)
|
||||
void CodeOneValue(TBitEncoder *bitEncoder, UInt32 symbol)
|
||||
{ bitEncoder->WriteBits(m_Items[symbol].Code, m_Items[symbol].Len); }
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// stdafx.h
|
||||
// StdAfx.h
|
||||
|
||||
#ifndef __STDAFX_H
|
||||
#define __STDAFX_H
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user