mirror of
https://github.com/hneemann/Digital.git
synced 2025-09-18 09:24:42 -04:00
made QMC much faster by using a better data structure tostore the QMC tables.
This commit is contained in:
parent
ea163b569f
commit
33a24aa418
@ -58,8 +58,8 @@ public class ModelAnalyser {
|
|||||||
|
|
||||||
if (inputs.size() == 0)
|
if (inputs.size() == 0)
|
||||||
throw new AnalyseException(Lang.get("err_analyseNoInputs"));
|
throw new AnalyseException(Lang.get("err_analyseNoInputs"));
|
||||||
if (inputs.size() > 9)
|
if (inputs.size() > 12)
|
||||||
throw new AnalyseException(Lang.get("err_toManyInputs_N", 9));
|
throw new AnalyseException(Lang.get("err_toManyInputs_N", 12));
|
||||||
if (outputs.size() == 0)
|
if (outputs.size() == 0)
|
||||||
throw new AnalyseException(Lang.get("err_analyseNoOutputs"));
|
throw new AnalyseException(Lang.get("err_analyseNoOutputs"));
|
||||||
rows = 1 << inputs.size();
|
rows = 1 << inputs.size();
|
||||||
|
@ -20,7 +20,7 @@ import static de.neemann.digital.analyse.expression.Operation.or;
|
|||||||
*/
|
*/
|
||||||
public class QuineMcCluskey {
|
public class QuineMcCluskey {
|
||||||
|
|
||||||
private final ArrayList<TableRow> rows;
|
private final TableRows rows;
|
||||||
private final ArrayList<Variable> variables;
|
private final ArrayList<Variable> variables;
|
||||||
private final ArrayList<TableRow> primes;
|
private final ArrayList<TableRow> primes;
|
||||||
|
|
||||||
@ -31,11 +31,11 @@ public class QuineMcCluskey {
|
|||||||
*/
|
*/
|
||||||
public QuineMcCluskey(ArrayList<Variable> variables) {
|
public QuineMcCluskey(ArrayList<Variable> variables) {
|
||||||
this.variables = variables;
|
this.variables = variables;
|
||||||
this.rows = new ArrayList<>();
|
this.rows = new TableRows();
|
||||||
this.primes = new ArrayList<>();
|
this.primes = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private QuineMcCluskey(ArrayList<Variable> variables, ArrayList<TableRow> rows, ArrayList<TableRow> primes) {
|
private QuineMcCluskey(ArrayList<Variable> variables, TableRows rows, ArrayList<TableRow> primes) {
|
||||||
this.variables = variables;
|
this.variables = variables;
|
||||||
this.rows = rows;
|
this.rows = rows;
|
||||||
this.primes = primes;
|
this.primes = primes;
|
||||||
@ -51,7 +51,7 @@ public class QuineMcCluskey {
|
|||||||
public QuineMcCluskey(Expression expression) throws ExpressionException {
|
public QuineMcCluskey(Expression expression) throws ExpressionException {
|
||||||
ContextFiller context = new ContextFiller(expression);
|
ContextFiller context = new ContextFiller(expression);
|
||||||
variables = context.getVariables();
|
variables = context.getVariables();
|
||||||
rows = new ArrayList<>();
|
rows = new TableRows();
|
||||||
fillTableWith(new BoolTableExpression(expression, context));
|
fillTableWith(new BoolTableExpression(expression, context));
|
||||||
primes = new ArrayList<>();
|
primes = new ArrayList<>();
|
||||||
}
|
}
|
||||||
@ -73,7 +73,6 @@ public class QuineMcCluskey {
|
|||||||
add(i, value.equals(ThreeStateValue.dontCare));
|
add(i, value.equals(ThreeStateValue.dontCare));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Collections.sort(rows);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,27 +128,29 @@ public class QuineMcCluskey {
|
|||||||
|
|
||||||
|
|
||||||
QuineMcCluskey simplifyStep() {
|
QuineMcCluskey simplifyStep() {
|
||||||
ArrayList<TableRow> newRows = new ArrayList<>();
|
TableRows newRows = new TableRows();
|
||||||
for (int i = 0; i < rows.size() - 1; i++)
|
|
||||||
for (int j = i + 1; j < rows.size(); j++) {
|
|
||||||
|
|
||||||
TableRow r1 = rows.get(i);
|
for (ArrayList<TableRow> list : rows.listIterable())
|
||||||
TableRow r2 = rows.get(j);
|
for (int i = 0; i < list.size() - 1; i++)
|
||||||
|
for (int j = i + 1; j < list.size(); j++) {
|
||||||
|
|
||||||
int index = checkCompatible(r1, r2);
|
TableRow r1 = list.get(i);
|
||||||
if (index >= 0) {
|
TableRow r2 = list.get(j);
|
||||||
// can optimize;
|
|
||||||
TableRow newRow = new TableRow(r1);
|
|
||||||
newRow.setToOptimized(index);
|
|
||||||
newRow.addSource(r1.getSource());
|
|
||||||
newRow.addSource(r2.getSource());
|
|
||||||
|
|
||||||
r1.setUsed();
|
int index = checkCompatible(r1, r2);
|
||||||
r2.setUsed();
|
if (index >= 0) {
|
||||||
|
// can optimize;
|
||||||
|
TableRow newRow = new TableRow(r1);
|
||||||
|
newRow.setToOptimized(index);
|
||||||
|
newRow.addSource(r1.getSource());
|
||||||
|
newRow.addSource(r2.getSource());
|
||||||
|
|
||||||
newRows.add(newRow);
|
r1.setUsed();
|
||||||
|
r2.setUsed();
|
||||||
|
|
||||||
|
newRows.add(newRow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<TableRow> np = new ArrayList<TableRow>();
|
ArrayList<TableRow> np = new ArrayList<TableRow>();
|
||||||
np.addAll(primes);
|
np.addAll(primes);
|
||||||
@ -161,13 +162,13 @@ public class QuineMcCluskey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QuineMcCluskey removeDuplicates() {
|
QuineMcCluskey removeDuplicates() {
|
||||||
ArrayList<TableRow> newRows = new ArrayList<TableRow>();
|
TableRows newRows = new TableRows();
|
||||||
for (TableRow r : rows) {
|
for (TableRow r : rows) {
|
||||||
int i = newRows.indexOf(r);
|
TableRow i = newRows.findRow(r);
|
||||||
if (i < 0) {
|
if (i == null) {
|
||||||
newRows.add(r);
|
newRows.add(r);
|
||||||
} else {
|
} else {
|
||||||
newRows.get(i).addSource(r.getSource());
|
i.addSource(r.getSource());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,12 +186,8 @@ public class QuineMcCluskey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int checkCompatible(TableRow r1, TableRow r2) {
|
private int checkCompatible(TableRow r1, TableRow r2) {
|
||||||
for (int i = 0; i < r1.size(); i++) {
|
if (r1.getOptimizedFlags() != r2.getOptimizedFlags())
|
||||||
if (r1.get(i) == TableItem.optimized || r2.get(i) == TableItem.optimized) {
|
return -1;
|
||||||
if (!r1.get(i).equals(r2.get(i)))
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int difIndex = -1;
|
int difIndex = -1;
|
||||||
for (int i = 0; i < r1.size(); i++) {
|
for (int i = 0; i < r1.size(); i++) {
|
||||||
@ -207,7 +204,12 @@ public class QuineMcCluskey {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
ArrayList<TableRow> newList = new ArrayList<TableRow>();
|
||||||
for (TableRow r : rows) {
|
for (TableRow r : rows) {
|
||||||
|
newList.add(r);
|
||||||
|
}
|
||||||
|
Collections.sort(newList);
|
||||||
|
for (TableRow r : newList) {
|
||||||
sb.append(r.toString());
|
sb.append(r.toString());
|
||||||
sb.append("\n");
|
sb.append("\n");
|
||||||
}
|
}
|
||||||
@ -240,7 +242,7 @@ public class QuineMcCluskey {
|
|||||||
* @param variables the variables to use to build the expression
|
* @param variables the variables to use to build the expression
|
||||||
* @return the expression
|
* @return the expression
|
||||||
*/
|
*/
|
||||||
public static Expression addAnd(Expression e, ArrayList<TableRow> rows, ArrayList<Variable> variables) {
|
public static Expression addAnd(Expression e, Iterable<TableRow> rows, ArrayList<Variable> variables) {
|
||||||
for (TableRow r : rows) {
|
for (TableRow r : rows) {
|
||||||
Expression n = r.getExpression(variables);
|
Expression n = r.getExpression(variables);
|
||||||
if (e == null)
|
if (e == null)
|
||||||
|
@ -22,8 +22,9 @@ import static de.neemann.digital.analyse.expression.Operation.and;
|
|||||||
public class TableRow implements Comparable<TableRow> {
|
public class TableRow implements Comparable<TableRow> {
|
||||||
|
|
||||||
private final TableItem[] items;
|
private final TableItem[] items;
|
||||||
private boolean used = false;
|
|
||||||
private final TreeSet<Integer> source;
|
private final TreeSet<Integer> source;
|
||||||
|
private boolean used = false;
|
||||||
|
private long optimizedFlags;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies the given table row
|
* Copies the given table row
|
||||||
@ -34,10 +35,11 @@ public class TableRow implements Comparable<TableRow> {
|
|||||||
this(tr.size());
|
this(tr.size());
|
||||||
for (int i = 0; i < size(); i++)
|
for (int i = 0; i < size(); i++)
|
||||||
items[i] = tr.get(i);
|
items[i] = tr.get(i);
|
||||||
|
optimizedFlags = tr.optimizedFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new tyble row
|
* Creates a new table row
|
||||||
*
|
*
|
||||||
* @param cols number of columns
|
* @param cols number of columns
|
||||||
*/
|
*/
|
||||||
@ -52,7 +54,7 @@ public class TableRow implements Comparable<TableRow> {
|
|||||||
* @param cols the number of columns
|
* @param cols the number of columns
|
||||||
* @param bitValue the value representing the bits in the row
|
* @param bitValue the value representing the bits in the row
|
||||||
* @param index the index of the original source row
|
* @param index the index of the original source row
|
||||||
* @param dontCare dont care
|
* @param dontCare true if don't care
|
||||||
*/
|
*/
|
||||||
public TableRow(int cols, int bitValue, int index, boolean dontCare) {
|
public TableRow(int cols, int bitValue, int index, boolean dontCare) {
|
||||||
this(cols);
|
this(cols);
|
||||||
@ -70,7 +72,7 @@ public class TableRow implements Comparable<TableRow> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The item at the given indes
|
* The item at the given index
|
||||||
*
|
*
|
||||||
* @param index the comumns index
|
* @param index the comumns index
|
||||||
* @return the value
|
* @return the value
|
||||||
@ -80,12 +82,23 @@ public class TableRow implements Comparable<TableRow> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the given idex to optimized
|
* Sets the given index to optimized
|
||||||
*
|
*
|
||||||
* @param index the columns index
|
* @param index the columns index
|
||||||
*/
|
*/
|
||||||
public void setToOptimized(int index) {
|
public void setToOptimized(int index) {
|
||||||
items[index] = TableItem.optimized;
|
items[index] = TableItem.optimized;
|
||||||
|
optimizedFlags |= 1L << index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the optimized flags.
|
||||||
|
* All Variables which are deleted/optimized in this row are marked by a one bit at their position.
|
||||||
|
*
|
||||||
|
* @return the flags
|
||||||
|
*/
|
||||||
|
public long getOptimizedFlags() {
|
||||||
|
return optimizedFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -151,7 +164,7 @@ public class TableRow implements Comparable<TableRow> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(TableRow tableRow) {
|
public int compareTo(TableRow tableRow) {
|
||||||
return Integer.compare(countOnes(), tableRow.countOnes());
|
return toString().compareTo(tableRow.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
124
src/main/java/de/neemann/digital/analyse/quinemc/TableRows.java
Normal file
124
src/main/java/de/neemann/digital/analyse/quinemc/TableRows.java
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package de.neemann.digital.analyse.quinemc;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set of rows stored in a special way to make comparisons faster
|
||||||
|
*
|
||||||
|
* @author hneemann
|
||||||
|
*/
|
||||||
|
class TableRows implements Iterable<TableRow> {
|
||||||
|
private final TreeMap<Long, ArrayList<TableRow>> rows;
|
||||||
|
private int size;
|
||||||
|
|
||||||
|
TableRows() {
|
||||||
|
rows = new TreeMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(TableRow tableRow) {
|
||||||
|
long flags = tableRow.getOptimizedFlags();
|
||||||
|
getList(flags).add(tableRow);
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<TableRow> getList(long flags) {
|
||||||
|
ArrayList<TableRow> list = rows.get(flags);
|
||||||
|
if (list == null) {
|
||||||
|
list = new ArrayList<>();
|
||||||
|
rows.put(flags, list);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
rows.clear();
|
||||||
|
size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAll(TableRows newRows) {
|
||||||
|
for (Map.Entry<Long, ArrayList<TableRow>> e : newRows.rows.entrySet()) {
|
||||||
|
ArrayList<TableRow> values = e.getValue();
|
||||||
|
getList(e.getKey()).addAll(values);
|
||||||
|
size += values.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return size == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the row stored in this list which is equal to the given row
|
||||||
|
*
|
||||||
|
* @param r the row to look for
|
||||||
|
* @return the row found
|
||||||
|
*/
|
||||||
|
TableRow findRow(TableRow r) {
|
||||||
|
ArrayList<TableRow> list = rows.get(r.getOptimizedFlags());
|
||||||
|
|
||||||
|
if (list == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
int i = list.indexOf(r);
|
||||||
|
if (i < 0)
|
||||||
|
return null;
|
||||||
|
else
|
||||||
|
return list.get(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<TableRow> iterator() {
|
||||||
|
return new RowIterator(rows.values().iterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterable<ArrayList<TableRow>> listIterable() {
|
||||||
|
return rows.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TableRow get(int i) {
|
||||||
|
for (Map.Entry<Long, ArrayList<TableRow>> e : rows.entrySet()) {
|
||||||
|
ArrayList<TableRow> list = e.getValue();
|
||||||
|
if (i < list.size())
|
||||||
|
return list.get(i);
|
||||||
|
else
|
||||||
|
i -= list.size();
|
||||||
|
}
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RowIterator implements Iterator<TableRow> {
|
||||||
|
private final Iterator<ArrayList<TableRow>> listIter;
|
||||||
|
private Iterator<TableRow> itemIter;
|
||||||
|
|
||||||
|
RowIterator(Iterator<ArrayList<TableRow>> iterator) {
|
||||||
|
listIter = iterator;
|
||||||
|
itemIter = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
while (itemIter == null && listIter.hasNext()) {
|
||||||
|
itemIter = listIter.next().iterator();
|
||||||
|
if (!itemIter.hasNext())
|
||||||
|
itemIter = null;
|
||||||
|
}
|
||||||
|
return itemIter != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableRow next() {
|
||||||
|
TableRow next = itemIter.next();
|
||||||
|
if (!itemIter.hasNext())
|
||||||
|
itemIter = null;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,14 +31,15 @@ public class QuineMcCluskeyTest extends TestCase {
|
|||||||
Expression e = or(and(a, and(c, d)), or(and(not(c), not(d)), and(not(b), c)));
|
Expression e = or(and(a, and(c, d)), or(and(not(c), not(d)), and(not(b), c)));
|
||||||
QuineMcCluskey t = new QuineMcCluskey(e);
|
QuineMcCluskey t = new QuineMcCluskey(e);
|
||||||
|
|
||||||
assertEquals("0000,1\n" +
|
assertEquals(
|
||||||
|
"0000,1\n" +
|
||||||
"0010,2\n" +
|
"0010,2\n" +
|
||||||
|
"0011,3\n" +
|
||||||
"0100,4\n" +
|
"0100,4\n" +
|
||||||
"1000,5\n" +
|
"1000,5\n" +
|
||||||
"0011,3\n" +
|
|
||||||
"1010,6\n" +
|
"1010,6\n" +
|
||||||
|
"1011,7\n" +
|
||||||
"1100,8\n" +
|
"1100,8\n" +
|
||||||
"1011,7\n" +
|
|
||||||
"1111,9\n", t.toString());
|
"1111,9\n", t.toString());
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -53,25 +54,28 @@ public class QuineMcCluskeyTest extends TestCase {
|
|||||||
QuineMcCluskey t = new QuineMcCluskey(e).simplifyStep();
|
QuineMcCluskey t = new QuineMcCluskey(e).simplifyStep();
|
||||||
assertFalse(t.isFinished());
|
assertFalse(t.isFinished());
|
||||||
|
|
||||||
assertEquals("00-0,1,2\n" +
|
assertEquals(
|
||||||
"0-00,1,4\n" +
|
|
||||||
"-000,1,5\n" +
|
"-000,1,5\n" +
|
||||||
|
"-010,2,6\n" +
|
||||||
|
"-011,3,7\n" +
|
||||||
|
"-100,4,8\n" +
|
||||||
|
"0-00,1,4\n" +
|
||||||
|
"00-0,1,2\n" +
|
||||||
"001-,2,3\n" +
|
"001-,2,3\n" +
|
||||||
"-010,2,6\n" +
|
"1-00,5,8\n" +
|
||||||
"-100,4,8\n" +
|
"1-11,7,9\n" +
|
||||||
"10-0,5,6\n" +
|
"10-0,5,6\n" +
|
||||||
"1-00,5,8\n" +
|
"101-,6,7\n",
|
||||||
"-011,3,7\n" +
|
t.toString());
|
||||||
"101-,6,7\n" +
|
|
||||||
"1-11,7,9\n", t.toString());
|
|
||||||
|
|
||||||
t = t.simplifyStep();
|
t = t.simplifyStep();
|
||||||
assertFalse(t.isFinished());
|
assertFalse(t.isFinished());
|
||||||
|
|
||||||
assertEquals("-0-0,1,2,5,6\n" +
|
assertEquals(
|
||||||
"--00,1,4,5,8\n" +
|
"--00,1,4,5,8\n" +
|
||||||
|
"--00,1,4,5,8\n" +
|
||||||
|
"-0-0,1,2,5,6\n" +
|
||||||
"-0-0,1,2,5,6\n" +
|
"-0-0,1,2,5,6\n" +
|
||||||
"--00,1,4,5,8\n" +
|
|
||||||
"-01-,2,3,6,7\n" +
|
"-01-,2,3,6,7\n" +
|
||||||
"-01-,2,3,6,7\n", t.toString());
|
"-01-,2,3,6,7\n", t.toString());
|
||||||
|
|
||||||
@ -82,8 +86,9 @@ public class QuineMcCluskeyTest extends TestCase {
|
|||||||
t = t.removeDuplicates();
|
t = t.removeDuplicates();
|
||||||
assertFalse(t.isFinished());
|
assertFalse(t.isFinished());
|
||||||
|
|
||||||
assertEquals("-0-0,1,2,5,6\n" +
|
assertEquals(
|
||||||
"--00,1,4,5,8\n" +
|
"--00,1,4,5,8\n" +
|
||||||
|
"-0-0,1,2,5,6\n" +
|
||||||
"-01-,2,3,6,7\n", t.toString());
|
"-01-,2,3,6,7\n", t.toString());
|
||||||
|
|
||||||
t = t.simplifyStep();
|
t = t.simplifyStep();
|
||||||
@ -94,8 +99,8 @@ public class QuineMcCluskeyTest extends TestCase {
|
|||||||
primes = t.getPrimes();
|
primes = t.getPrimes();
|
||||||
assertEquals(4, primes.size());
|
assertEquals(4, primes.size());
|
||||||
assertEquals("1-11,7,9", primes.get(0).toString());
|
assertEquals("1-11,7,9", primes.get(0).toString());
|
||||||
assertEquals("-0-0,1,2,5,6", primes.get(1).toString());
|
assertEquals("-0-0,1,2,5,6", primes.get(2).toString());
|
||||||
assertEquals("--00,1,4,5,8", primes.get(2).toString());
|
assertEquals("--00,1,4,5,8", primes.get(1).toString());
|
||||||
assertEquals("-01-,2,3,6,7", primes.get(3).toString());
|
assertEquals("-01-,2,3,6,7", primes.get(3).toString());
|
||||||
|
|
||||||
Expression exp = t.getExpression();
|
Expression exp = t.getExpression();
|
||||||
|
@ -83,7 +83,7 @@ public class Gal16V8JEDECExporterTest extends TestCase {
|
|||||||
"QF2194*\r\n" +
|
"QF2194*\r\n" +
|
||||||
"G0*\r\n" +
|
"G0*\r\n" +
|
||||||
"F0*\r\n" +
|
"F0*\r\n" +
|
||||||
"L256 10111110110111011111111111111111*\r\n" +
|
"L256 10111110110111011111111111111111*\r\n" + // fuses are created with WinCUPL 5.0
|
||||||
"L288 10111101111011111111111111111111*\r\n" +
|
"L288 10111101111011111111111111111111*\r\n" +
|
||||||
"L320 01111110111011101111111111111111*\r\n" +
|
"L320 01111110111011101111111111111111*\r\n" +
|
||||||
"L352 01111101111111011111111111111111*\r\n" +
|
"L352 01111101111111011111111111111111*\r\n" +
|
||||||
|
Loading…
x
Reference in New Issue
Block a user