mirror of
https://github.com/dmolony/DiskBrowser.git
synced 2025-01-02 06:31:03 +00:00
removed dependency on Common
This commit is contained in:
parent
9c9cf87ca8
commit
0fe2a21f96
@ -133,6 +133,7 @@ public class DiskBrowser extends JFrame implements DiskSelectionListener, QuitLi
|
||||
|
||||
// restore the menuHandler items before they are referenced
|
||||
quitAction.restore ();
|
||||
diskLayoutPanel.setFree (menuHandler.showFreeSectorsItem.isSelected ());
|
||||
|
||||
// Remove the two optional panels if they were previously hidden
|
||||
if (!menuHandler.showLayoutItem.isSelected ())
|
||||
|
@ -16,14 +16,14 @@ class ShowFreeSectorsAction extends AbstractAction
|
||||
{
|
||||
super ("Show free sectors");
|
||||
putValue (Action.SHORT_DESCRIPTION,
|
||||
"Display which sectors are marked free in the disk layout panel");
|
||||
"Display which sectors are marked free in the disk layout panel");
|
||||
putValue (Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke ("alt F"));
|
||||
putValue (Action.MNEMONIC_KEY, KeyEvent.VK_F);
|
||||
this.panel = panel;
|
||||
this.mh = mh;
|
||||
panel.setFree (mh.showFreeSectorsItem.isSelected ()); // set initial state
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed (ActionEvent e)
|
||||
{
|
||||
panel.setFree (mh.showFreeSectorsItem.isSelected ());
|
||||
|
@ -41,13 +41,19 @@ class FileEntry extends CatalogEntry implements ProdosConstants
|
||||
this.catalogBlock = this.disk.getDiskAddress (parentBlock);
|
||||
|
||||
fileType = entryBuffer[16] & 0xFF;
|
||||
keyPtr = HexFormatter.intValue (entryBuffer[17], entryBuffer[18]);
|
||||
blocksUsed = HexFormatter.intValue (entryBuffer[19], entryBuffer[20]);
|
||||
// keyPtr = HexFormatter.intValue (entryBuffer[17], entryBuffer[18]);
|
||||
keyPtr = HexFormatter.unsignedShort (entryBuffer, 17);
|
||||
// System.out.printf ("%5d %5d%n",
|
||||
// HexFormatter.intValue (entryBuffer[17], entryBuffer[18]), keyPtr);
|
||||
// blocksUsed = HexFormatter.intValue (entryBuffer[19], entryBuffer[20]);
|
||||
blocksUsed = HexFormatter.unsignedShort (entryBuffer, 19);
|
||||
endOfFile = HexFormatter.intValue (entryBuffer[21], entryBuffer[22], entryBuffer[23]);
|
||||
|
||||
auxType = HexFormatter.intValue (entryBuffer[31], entryBuffer[32]);
|
||||
// auxType = HexFormatter.intValue (entryBuffer[31], entryBuffer[32]);
|
||||
auxType = HexFormatter.unsignedShort (entryBuffer, 31);
|
||||
modified = HexFormatter.getAppleDate (entryBuffer, 33);
|
||||
headerPointer = HexFormatter.intValue (entryBuffer[37], entryBuffer[38]);
|
||||
// headerPointer = HexFormatter.intValue (entryBuffer[37], entryBuffer[38]);
|
||||
headerPointer = HexFormatter.unsignedShort (entryBuffer, 37);
|
||||
|
||||
switch (storageType)
|
||||
{
|
||||
@ -70,6 +76,7 @@ class FileEntry extends CatalogEntry implements ProdosConstants
|
||||
case TYPE_TREE:
|
||||
parentDisk.setSectorType (keyPtr, fDisk.masterIndexSector);
|
||||
masterIndexBlock = disk.getDiskAddress (keyPtr);
|
||||
indexBlocks.add (masterIndexBlock);
|
||||
if (isGEOSFile ())
|
||||
traverseGEOSMasterIndex (keyPtr);
|
||||
else
|
||||
@ -113,6 +120,10 @@ class FileEntry extends CatalogEntry implements ProdosConstants
|
||||
} while (block > 0);
|
||||
break;
|
||||
|
||||
case TYPE_PASCAL_ON_PROFILE:
|
||||
indexBlocks.add (disk.getDiskAddress (keyPtr));
|
||||
break;
|
||||
|
||||
default:
|
||||
System.out.println ("Unknown storage type: " + storageType);
|
||||
}
|
||||
@ -365,8 +376,11 @@ class FileEntry extends CatalogEntry implements ProdosConstants
|
||||
case FILE_TYPE_GSOS_FILE_SYSTEM_TRANSLATOR:
|
||||
file = new FileSystemTranslator (name, exactBuffer);
|
||||
break;
|
||||
case FILE_TYPE_PASCAL_VOLUME:
|
||||
file = new DefaultAppleFile (name, exactBuffer);
|
||||
break;
|
||||
default:
|
||||
System.out.format ("%s - Unknown file type : %02X%n", name, fileType);
|
||||
System.out.format ("%s - Unknown Prodos file type : %02X%n", name, fileType);
|
||||
file = new DefaultAppleFile (name, exactBuffer);
|
||||
}
|
||||
}
|
||||
@ -502,6 +516,9 @@ class FileEntry extends CatalogEntry implements ProdosConstants
|
||||
case TYPE_GSOS_EXTENDED_FILE:
|
||||
return disk.readSectors (dataBlocks); // data and resource forks concatenated
|
||||
|
||||
case TYPE_PASCAL_ON_PROFILE:
|
||||
return disk.readSectors (dataBlocks);
|
||||
|
||||
default:
|
||||
System.out.println ("Unknown storage type in getBuffer : " + storageType);
|
||||
return new byte[512];
|
||||
|
@ -33,7 +33,7 @@ class ProdosBitMapSector extends AbstractSector
|
||||
|
||||
int width = (grid.width - 1) / 8 + 1; // must be 1-4
|
||||
|
||||
StringBuilder text = getHeader ("Prodos Bit Map Sector");
|
||||
StringBuilder text = getHeader ("Volume Bit Map Block");
|
||||
|
||||
if (false)
|
||||
{
|
||||
|
@ -23,7 +23,7 @@ class ProdosCatalogSector extends AbstractSector
|
||||
@Override
|
||||
public String createText ()
|
||||
{
|
||||
StringBuilder text = getHeader ("Prodos Catalog Sector");
|
||||
StringBuilder text = getHeader ("Volume Directory Block");
|
||||
|
||||
addTextAndDecimal (text, buffer, 0, 2, "Previous block");
|
||||
addTextAndDecimal (text, buffer, 2, 2, "Next block");
|
||||
|
@ -22,6 +22,7 @@ public interface ProdosConstants
|
||||
int FILE_TYPE_FONT = 0xC8;
|
||||
int FILE_TYPE_ICN = 0xCA;
|
||||
int FILE_TYPE_APPLETALK = 0xE2;
|
||||
int FILE_TYPE_PASCAL_VOLUME = 0xEF;
|
||||
int FILE_TYPE_USER_DEFINED_1 = 0xF1;
|
||||
int FILE_TYPE_INTEGER_BASIC = 0xFA;
|
||||
int FILE_TYPE_INTEGER_BASIC_VARS = 0xFB;
|
||||
@ -70,7 +71,7 @@ public interface ProdosConstants
|
||||
"$D0", "$D1", "$D2", "$D3", "$D4", "MUS", "INS", "MDI", //
|
||||
"SND", "$D9", "$DA", "DBM", "$DC", "DDD", "$DE", "$DF", //
|
||||
"LBR", "$E1", "ATK", "$E3", "$E4", "$E5", "$E6", "$E7", //
|
||||
"$E8", "$E9", "$EA", "$EB", "$EC", "$ED", "R16", "PAS", //
|
||||
"$E8", "$E9", "$EA", "$EB", "$EC", "$ED", "R16", "PAR", //
|
||||
"CMD", "OVL", "UD2", "UD3", "UD4", "BAT", "UD6", "UD7", //
|
||||
"PRG", "P16", "INT", "IVR", "BAS", "VAR", "REL", "SYS" };
|
||||
|
||||
@ -206,4 +207,37 @@ public interface ProdosConstants
|
||||
* $FF SYS ProDOS System File
|
||||
*/
|
||||
|
||||
// See also http://www.kreativekorp.com/miscpages/a2info/filetypes.shtml
|
||||
// See also http://www.kreativekorp.com/miscpages/a2info/filetypes.shtml
|
||||
|
||||
/*
|
||||
* https://groups.google.com/forum/#!topic/comp.sys.apple2/waoYCIbkJKs
|
||||
*
|
||||
* There are a number of disk utilities available that store images of disks that
|
||||
* utilize file systems that are not ProDOS, at the end of a ProDOS volume.
|
||||
* There's DOS Master, by Glen Bredon, that stores images of DOS 3.3 disks at the
|
||||
* end of a ProDOS volume. Similarly, Pro/Part, by Steven Hirsch, stores images
|
||||
* of CP/M volumes. Also, there's Pascal Partition Manager (PPM) that stores
|
||||
* images of UCSD Pascal volumes. I've decided to refer to the area used to store
|
||||
* volume images, by all three of these systems, as a Foreign Volume Area or FVA.
|
||||
* All three of these systems modify the Block Allocation Map of a ProDOS volume
|
||||
* to keep ProDOS from assigning blocks used by FVAs for use by files being
|
||||
* written by ProDOS. Pascal Partition Manager is different from the other two
|
||||
* in that it has a file type ($EF) and file kind (4) assigned to it by Apple.
|
||||
* A directory listing of a ProODS volume containing an FVA managed by PPM will
|
||||
* show a file name of "PASCAL.AREA". A directory listing of a ProDOS volume
|
||||
* containing an FVA managed by DOS Master or Pro/Part will show absolutely nothing.
|
||||
* Running a popular utility named "MR.FIXIT", also by Glen Bredon, against a
|
||||
* ProDOS volume containing an FVA will report an error. Specifically, "MR.FIXIT"
|
||||
* will complain that all the blocks used by an FVA as allocated but not in use.
|
||||
* To solve this problem for Pro/Part I wrote a Foreign Volume Area utility
|
||||
* program that generates a directory entry for the Pro/Part area. That entry has
|
||||
* file kind 4, file type $EF, file name "PROPART.AREA" and an auxiliary file
|
||||
* type $4853 (Steven Hirsch's initials). Today I realized that it's likely that
|
||||
* the same thing could be done for DOS Master. Study of the source code for
|
||||
* DOS Master will reveal it that's true. If it is, I propose that "DOS33.AREA"
|
||||
* be used as the file name and $4247 as the auxiliary type (Glen Bredon's initials).
|
||||
* As I compose the text of this message I realize that another solution is to
|
||||
* modify "MR.FIXIT" to be aware of FVAs. But doing that would not allow someone
|
||||
* doing a directory listing of a ProDOS volume containing an FVA to be aware
|
||||
* that the FVA exists.
|
||||
*/
|
@ -18,21 +18,22 @@ class ProdosIndexSector extends AbstractSector
|
||||
@Override
|
||||
public String createText ()
|
||||
{
|
||||
StringBuilder text = getHeader ("Prodos Index Sector : " + name);
|
||||
StringBuilder text = getHeader ("Prodos Index Block : " + name);
|
||||
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
text.append (String.format ("%02X %02X %02X", i, buffer[i],
|
||||
buffer[i + 256]));
|
||||
text.append (
|
||||
String.format ("%02X %02X %02X", i, buffer[i], buffer[i + 256]));
|
||||
if (buffer[i] != 0 || buffer[i + 256] != 0)
|
||||
text.append (String
|
||||
.format (" %s%n",
|
||||
"block " + HexFormatter.intValue (buffer[i], buffer[i + 256])));
|
||||
text.append (String.format (" %s%n",
|
||||
"block " + HexFormatter.intValue (buffer[i], buffer[i + 256])));
|
||||
else
|
||||
text.append ("\n");
|
||||
}
|
||||
|
||||
if (text.length () > 0)
|
||||
text.deleteCharAt (text.length () - 1);
|
||||
|
||||
return text.toString ();
|
||||
}
|
||||
}
|
@ -23,9 +23,9 @@ class VolumeDirectoryHeader extends DirectoryHeader
|
||||
{
|
||||
super (parentDisk, entryBuffer);
|
||||
|
||||
bitMapBlock = HexFormatter.intValue (entryBuffer[35], entryBuffer[36]);
|
||||
totalBlocks = HexFormatter.intValue (entryBuffer[37], entryBuffer[38]);
|
||||
if (totalBlocks == 0xFFFF | totalBlocks == 0x7FFF)
|
||||
bitMapBlock = HexFormatter.unsignedShort (entryBuffer, 35);
|
||||
totalBlocks = HexFormatter.unsignedShort (entryBuffer, 37);
|
||||
if (totalBlocks == 0xFFFF || totalBlocks == 0x7FFF)
|
||||
totalBlocks = (int) disk.getFile ().length () / 4096 * 8; // ignore extra bytes
|
||||
// totalBitMapBlocks = (totalBlocks * 8 - 1) / 4096 + 1;
|
||||
totalBitMapBlocks = (totalBlocks - 1) / 512 + 1;
|
||||
@ -35,7 +35,7 @@ class VolumeDirectoryHeader extends DirectoryHeader
|
||||
{
|
||||
dataBlocks.add (disk.getDiskAddress (block));
|
||||
byte[] buffer = disk.readSector (block);
|
||||
block = HexFormatter.intValue (buffer[2], buffer[3]);
|
||||
block = HexFormatter.unsignedShort (buffer, 2);
|
||||
} while (block > 0);
|
||||
|
||||
// convert the Free Sector Table
|
||||
|
@ -329,13 +329,14 @@ public class HexFormatter
|
||||
|
||||
public static int unsignedShort (byte[] buffer, int ptr)
|
||||
{
|
||||
int val = 0;
|
||||
for (int i = 1; i >= 0; i--)
|
||||
{
|
||||
val <<= 8;
|
||||
val += buffer[ptr + i] & 0xFF;
|
||||
}
|
||||
return val;
|
||||
// int val = 0;
|
||||
// for (int i = 1; i >= 0; i--)
|
||||
// {
|
||||
// val <<= 8;
|
||||
// val += buffer[ptr + i] & 0xFF;
|
||||
// }
|
||||
// return val;
|
||||
return (buffer[ptr] & 0xFF) | ((buffer[ptr + 1] & 0xFF) << 8);
|
||||
}
|
||||
|
||||
// public static int signedShort (byte[] buffer, int ptr)
|
||||
|
@ -110,4 +110,16 @@ class LZW
|
||||
{
|
||||
return 32 - Integer.numberOfLeadingZeros (maximumValue);
|
||||
}
|
||||
|
||||
static int getLong (byte[] buffer, int ptr)
|
||||
{
|
||||
return getWord (buffer, ptr) + getWord (buffer, ptr + 2) * 0x10000;
|
||||
}
|
||||
|
||||
static int getWord (byte[] buffer, int ptr)
|
||||
{
|
||||
int a = (buffer[ptr + 1] & 0xFF) << 8;
|
||||
int b = buffer[ptr] & 0xFF;
|
||||
return a + b;
|
||||
}
|
||||
}
|
@ -2,15 +2,13 @@ package com.bytezone.diskbrowser.utilities;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.bytezone.common.Utility;
|
||||
|
||||
class LZW1 extends LZW
|
||||
{
|
||||
public LZW1 (byte[] buffer)
|
||||
{
|
||||
bytes = Objects.requireNonNull (buffer);
|
||||
|
||||
crc = Utility.getWord (buffer, 0);
|
||||
crc = LZW.getWord (buffer, 0);
|
||||
crcBase = 0;
|
||||
|
||||
volume = buffer[2] & 0xFF;
|
||||
@ -19,7 +17,7 @@ class LZW1 extends LZW
|
||||
|
||||
while (ptr < buffer.length - 1) // what is in the last byte?
|
||||
{
|
||||
int rleLength = Utility.getWord (buffer, ptr);
|
||||
int rleLength = LZW.getWord (buffer, ptr);
|
||||
int lzwPerformed = buffer[ptr + 2] & 0xFF;
|
||||
ptr += 3;
|
||||
|
||||
|
@ -2,8 +2,6 @@ package com.bytezone.diskbrowser.utilities;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.bytezone.common.Utility;
|
||||
|
||||
class LZW2 extends LZW
|
||||
{
|
||||
private int nextEntry = 0x100;
|
||||
@ -24,7 +22,7 @@ class LZW2 extends LZW
|
||||
|
||||
while (ptr < buffer.length - 1) // what is in the last byte?
|
||||
{
|
||||
int rleLength = Utility.getWord (buffer, ptr);
|
||||
int rleLength = LZW.getWord (buffer, ptr);
|
||||
boolean lzwPerformed = (rleLength & 0x8000) != 0;
|
||||
ptr += 2;
|
||||
|
||||
@ -34,7 +32,7 @@ class LZW2 extends LZW
|
||||
if (rleLength == 0)
|
||||
rleLength = TRACK_LENGTH;
|
||||
|
||||
int chunkLength = Utility.getWord (buffer, ptr);
|
||||
int chunkLength = LZW.getWord (buffer, ptr);
|
||||
ptr += 2;
|
||||
|
||||
setBuffer (buffer, ptr); // prepare to read n-bit integers
|
||||
|
@ -7,8 +7,6 @@ import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.bytezone.common.Utility;
|
||||
|
||||
public class NuFX
|
||||
{
|
||||
private static String[] fileSystems =
|
||||
@ -131,12 +129,12 @@ public class NuFX
|
||||
throw new FileFormatException ("NuFile not found");
|
||||
}
|
||||
|
||||
crc = Utility.getWord (buffer, ptr + 6);
|
||||
totalRecords = Utility.getLong (buffer, ptr + 8);
|
||||
crc = LZW.getWord (buffer, ptr + 6);
|
||||
totalRecords = LZW.getLong (buffer, ptr + 8);
|
||||
created = new DateTime (buffer, ptr + 12);
|
||||
modified = new DateTime (buffer, ptr + 20);
|
||||
version = Utility.getWord (buffer, ptr + 28);
|
||||
eof = Utility.getLong (buffer, ptr + 38);
|
||||
version = LZW.getWord (buffer, ptr + 28);
|
||||
eof = LZW.getLong (buffer, ptr + 38);
|
||||
|
||||
byte[] crcBuffer = new byte[40];
|
||||
System.arraycopy (buffer, ptr + 8, crcBuffer, 0, crcBuffer.length);
|
||||
@ -205,21 +203,21 @@ public class NuFX
|
||||
if (!isNuFX (buffer, dataPtr))
|
||||
throw new FileFormatException ("NuFX not found");
|
||||
|
||||
crc = Utility.getWord (buffer, dataPtr + 4);
|
||||
attributes = Utility.getWord (buffer, dataPtr + 6);
|
||||
version = Utility.getWord (buffer, dataPtr + 8);
|
||||
totThreads = Utility.getLong (buffer, dataPtr + 10);
|
||||
fileSystemID = Utility.getWord (buffer, dataPtr + 14);
|
||||
crc = LZW.getWord (buffer, dataPtr + 4);
|
||||
attributes = LZW.getWord (buffer, dataPtr + 6);
|
||||
version = LZW.getWord (buffer, dataPtr + 8);
|
||||
totThreads = LZW.getLong (buffer, dataPtr + 10);
|
||||
fileSystemID = LZW.getWord (buffer, dataPtr + 14);
|
||||
separator = (char) (buffer[dataPtr + 16] & 0x00FF);
|
||||
access = Utility.getLong (buffer, dataPtr + 18);
|
||||
fileType = Utility.getLong (buffer, dataPtr + 22);
|
||||
auxType = Utility.getLong (buffer, dataPtr + 26);
|
||||
storType = Utility.getWord (buffer, dataPtr + 30);
|
||||
access = LZW.getLong (buffer, dataPtr + 18);
|
||||
fileType = LZW.getLong (buffer, dataPtr + 22);
|
||||
auxType = LZW.getLong (buffer, dataPtr + 26);
|
||||
storType = LZW.getWord (buffer, dataPtr + 30);
|
||||
created = new DateTime (buffer, dataPtr + 32);
|
||||
modified = new DateTime (buffer, dataPtr + 40);
|
||||
archived = new DateTime (buffer, dataPtr + 48);
|
||||
optionSize = Utility.getWord (buffer, dataPtr + 56);
|
||||
fileNameLength = Utility.getWord (buffer, dataPtr + attributes - 2);
|
||||
optionSize = LZW.getWord (buffer, dataPtr + 56);
|
||||
fileNameLength = LZW.getWord (buffer, dataPtr + attributes - 2);
|
||||
|
||||
int len = attributes + fileNameLength - 6;
|
||||
byte[] crcBuffer = new byte[len + totThreads * 16];
|
||||
|
@ -1,18 +1,16 @@
|
||||
package com.bytezone.diskbrowser.utilities;
|
||||
|
||||
import com.bytezone.common.Utility;
|
||||
|
||||
class Thread
|
||||
{
|
||||
private static String[] threadClassText = { "Message", "Control", "Data", "Filename" };
|
||||
private static String[] formatText = { //
|
||||
"Uncompressed", "Huffman squeeze", "LZW/1", "LZW/2", "Unix 12-bit Compress",
|
||||
"Unix 16-bit Compress" };
|
||||
private static String[][] threadKindText = {//
|
||||
{ "ASCII text", "predefined EOF", "IIgs icon" },
|
||||
{ "create directory", "undefined", "undefined" },
|
||||
{ "data fork", "disk image", "resource fork" },
|
||||
{ "filename", "undefined", "undefined" } };
|
||||
private static String[] formatText =
|
||||
{ "Uncompressed", "Huffman squeeze", "LZW/1", "LZW/2", "Unix 12-bit Compress",
|
||||
"Unix 16-bit Compress" };
|
||||
private static String[][] threadKindText =
|
||||
{ { "ASCII text", "predefined EOF", "IIgs icon" },
|
||||
{ "create directory", "undefined", "undefined" },
|
||||
{ "data fork", "disk image", "resource fork" },
|
||||
{ "filename", "undefined", "undefined" } };
|
||||
|
||||
private final ThreadHeader header;
|
||||
private final byte[] data;
|
||||
@ -106,13 +104,12 @@ class Thread
|
||||
|
||||
public ThreadHeader (byte[] buffer, int offset)
|
||||
{
|
||||
threadClass = Utility.getWord (buffer, offset);
|
||||
format = Utility.getWord (buffer, offset + 2);
|
||||
threadKind = Utility.getWord (buffer, offset + 4);
|
||||
crc = Utility.getWord (buffer, offset + 6);
|
||||
uncompressedEOF = Utility.getLong (buffer, offset + 8);
|
||||
compressedEOF = Utility.getLong (buffer, offset + 12);
|
||||
// System.out.println (Utility.toHex (buffer, offset, 16));
|
||||
threadClass = LZW.getWord (buffer, offset);
|
||||
format = LZW.getWord (buffer, offset + 2);
|
||||
threadKind = LZW.getWord (buffer, offset + 4);
|
||||
crc = LZW.getWord (buffer, offset + 6);
|
||||
uncompressedEOF = LZW.getLong (buffer, offset + 8);
|
||||
compressedEOF = LZW.getLong (buffer, offset + 12);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -121,15 +118,15 @@ class Thread
|
||||
StringBuilder text = new StringBuilder ();
|
||||
|
||||
text.append (String.format (" threadClass ....... %d %s%n", threadClass,
|
||||
threadClassText[threadClass]));
|
||||
text.append (String
|
||||
.format (" format ............ %d %s%n", format, formatText[format]));
|
||||
threadClassText[threadClass]));
|
||||
text.append (
|
||||
String.format (" format ............ %d %s%n", format, formatText[format]));
|
||||
text.append (String.format (" kind .............. %d %s%n", threadKind,
|
||||
threadKindText[threadClass][threadKind]));
|
||||
threadKindText[threadClass][threadKind]));
|
||||
text.append (String.format (" crc ............... %,d%n", crc));
|
||||
text.append (String.format (" uncompressedEOF ... %,d%n", uncompressedEOF));
|
||||
text.append (String.format (" compressedEOF ..... %,d (%08X)", compressedEOF,
|
||||
compressedEOF));
|
||||
compressedEOF));
|
||||
return text.toString ();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user