mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-07-13 15:38:25 +00:00
AS3 pcode search improvements
This commit is contained in:
@@ -41,6 +41,7 @@ import com.jpexs.decompiler.flash.abc.types.traits.TraitClass;
|
||||
import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction;
|
||||
import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter;
|
||||
import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst;
|
||||
import com.jpexs.decompiler.flash.abc.types.traits.TraitType;
|
||||
import com.jpexs.decompiler.flash.abc.types.traits.Traits;
|
||||
import com.jpexs.decompiler.flash.abc.usages.ClassNameMultinameUsage;
|
||||
import com.jpexs.decompiler.flash.abc.usages.ConstVarNameMultinameUsage;
|
||||
@@ -58,6 +59,7 @@ import com.jpexs.decompiler.flash.abc.usages.TypeNameMultinameUsage;
|
||||
import com.jpexs.decompiler.flash.dumpview.DumpInfo;
|
||||
import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecial;
|
||||
import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType;
|
||||
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
|
||||
import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin;
|
||||
import com.jpexs.decompiler.flash.importers.As3ScriptReplaceException;
|
||||
import com.jpexs.decompiler.flash.importers.As3ScriptReplacerInterface;
|
||||
@@ -1426,6 +1428,30 @@ public class ABC {
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getGlobalTraitId(TraitType type, boolean isStatic, int classIndex, int index) {
|
||||
if (type == TraitType.INITIALIZER) {
|
||||
if (!isStatic) {
|
||||
return GraphTextWriter.TRAIT_INSTANCE_INITIALIZER;
|
||||
} else {
|
||||
return GraphTextWriter.TRAIT_CLASS_INITIALIZER;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == TraitType.SCRIPT_INITIALIZER) {
|
||||
return GraphTextWriter.TRAIT_SCRIPT_INITIALIZER;
|
||||
}
|
||||
|
||||
if (classIndex == -1) {
|
||||
return index;
|
||||
}
|
||||
|
||||
if (isStatic) {
|
||||
return index;
|
||||
} else {
|
||||
return class_info.get(classIndex).static_traits.traits.size() + index;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeClassFromTraits(Traits traits, int index) {
|
||||
for (Trait t : traits.traits) {
|
||||
if (t instanceof TraitClass) {
|
||||
|
||||
@@ -620,12 +620,12 @@ public class ScriptPack extends AS3ClassTreeItem {
|
||||
|
||||
public void getMethodInfos(List<MethodId> methodInfos) {
|
||||
int script_init = abc.script_info.get(scriptIndex).init_index;
|
||||
methodInfos.add(new MethodId(-1, script_init));
|
||||
methodInfos.add(new MethodId(GraphTextWriter.TRAIT_SCRIPT_INITIALIZER, -1, script_init));
|
||||
|
||||
List<Trait> traits = abc.script_info.get(scriptIndex).traits.traits;
|
||||
for (int t = 0; t < traitIndices.size(); t++) {
|
||||
for (int t : traitIndices) {
|
||||
Trait trait = traits.get(t);
|
||||
trait.getMethodInfos(abc, -1, methodInfos);
|
||||
trait.getMethodInfos(abc, GraphTextWriter.TRAIT_UNKNOWN, -1, methodInfos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,5 +511,5 @@ public abstract class Trait implements Cloneable, Serializable {
|
||||
return true;
|
||||
}
|
||||
|
||||
public abstract void getMethodInfos(ABC abc, int classIndex, List<MethodId> methodInfos);
|
||||
public abstract void getMethodInfos(ABC abc, int traitId, int classIndex, List<MethodId> methodInfos);
|
||||
}
|
||||
|
||||
@@ -278,19 +278,19 @@ public class TraitClass extends Trait implements TraitWithSlot {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getMethodInfos(ABC abc, int classIndex, List<MethodId> methodInfos) {
|
||||
public void getMethodInfos(ABC abc, int traitId, int classIndex, List<MethodId> methodInfos) {
|
||||
InstanceInfo instanceInfo = abc.instance_info.get(class_info);
|
||||
ClassInfo classInfo = abc.class_info.get(class_info);
|
||||
|
||||
//class initializer
|
||||
methodInfos.add(new MethodId(class_info, classInfo.cinit_index));
|
||||
methodInfos.add(new MethodId(GraphTextWriter.TRAIT_CLASS_INITIALIZER, class_info, classInfo.cinit_index));
|
||||
|
||||
//constructor - instance initializer
|
||||
methodInfos.add(new MethodId(class_info, instanceInfo.iinit_index));
|
||||
methodInfos.add(new MethodId(GraphTextWriter.TRAIT_INSTANCE_INITIALIZER, class_info, instanceInfo.iinit_index));
|
||||
|
||||
//static variables,constants & methods
|
||||
classInfo.static_traits.getMethodInfos(abc, class_info, methodInfos);
|
||||
classInfo.static_traits.getMethodInfos(abc, true, class_info, methodInfos);
|
||||
|
||||
instanceInfo.instance_traits.getMethodInfos(abc, class_info, methodInfos);
|
||||
instanceInfo.instance_traits.getMethodInfos(abc, false, class_info, methodInfos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ public class TraitFunction extends Trait implements TraitWithSlot {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getMethodInfos(ABC abc, int classIndex, List<MethodId> methodInfos) {
|
||||
methodInfos.add(new MethodId(classIndex, method_info));
|
||||
public void getMethodInfos(ABC abc, int traitId, int classIndex, List<MethodId> methodInfos) {
|
||||
methodInfos.add(new MethodId(traitId, classIndex, method_info));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ public class TraitMethodGetterSetter extends Trait {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getMethodInfos(ABC abc, int classIndex, List<MethodId> methodInfos) {
|
||||
methodInfos.add(new MethodId(classIndex, method_info));
|
||||
public void getMethodInfos(ABC abc, int traitId, int classIndex, List<MethodId> methodInfos) {
|
||||
methodInfos.add(new MethodId(traitId, classIndex, method_info));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,6 +247,6 @@ public class TraitSlotConst extends Trait implements TraitWithSlot {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getMethodInfos(ABC abc, int classIndex, List<MethodId> methodInfos) {
|
||||
public void getMethodInfos(ABC abc, int traitId, int classIndex, List<MethodId> methodInfos) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2016 JPEXS
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.abc.types.traits;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public enum TraitType {
|
||||
METHOD,
|
||||
VAR,
|
||||
CONST,
|
||||
INITIALIZER,
|
||||
SCRIPT_INITIALIZER;
|
||||
|
||||
public static TraitType getTypeForTrait(Trait t) {
|
||||
if (t instanceof TraitMethodGetterSetter) {
|
||||
return METHOD;
|
||||
}
|
||||
if (t instanceof TraitSlotConst) {
|
||||
if (((TraitSlotConst) t).isConst()) {
|
||||
return CONST;
|
||||
} else {
|
||||
return VAR;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -171,12 +171,7 @@ public class Traits implements Cloneable, Serializable {
|
||||
continue;
|
||||
}
|
||||
writer.newLine();
|
||||
int h = t;
|
||||
if (classIndex != -1) {
|
||||
if (!isStatic) {
|
||||
h += abc.class_info.get(classIndex).static_traits.traits.size();
|
||||
}
|
||||
}
|
||||
int h = abc.getGlobalTraitId(TraitType.METHOD /*non-initializer*/, isStatic, classIndex, t);
|
||||
if (trait instanceof TraitClass) {
|
||||
writer.startClass(((TraitClass) trait).class_info);
|
||||
} else {
|
||||
@@ -252,10 +247,10 @@ public class Traits implements Cloneable, Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
public void getMethodInfos(ABC abc, int classIndex, List<MethodId> methodInfos) {
|
||||
public void getMethodInfos(ABC abc, boolean isStatic, int classIndex, List<MethodId> methodInfos) {
|
||||
for (int t = 0; t < traits.size(); t++) {
|
||||
Trait trait = traits.get(t);
|
||||
trait.getMethodInfos(abc, classIndex, methodInfos);
|
||||
trait.getMethodInfos(abc, abc.getGlobalTraitId(TraitType.METHOD /*non-initializer*/, isStatic, classIndex, t), classIndex, methodInfos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ public abstract class GraphTextWriter {
|
||||
|
||||
public static final int TRAIT_SCRIPT_INITIALIZER = -3;
|
||||
|
||||
public static final int TRAIT_UNKNOWN = -4;
|
||||
|
||||
public CodeFormatting getFormatting() {
|
||||
return formatting;
|
||||
}
|
||||
|
||||
@@ -36,3 +36,7 @@ timeFormat.second = second
|
||||
timeFormat.seconds = seconds
|
||||
|
||||
fontNotFound = Font with id=%fontId% was not found.
|
||||
|
||||
trait.scriptinitializer = script initializer
|
||||
trait.instanceinitializer = instance initializer
|
||||
trait.classinitializer = class initializer
|
||||
|
||||
@@ -36,3 +36,7 @@ timeFormat.second = segon
|
||||
timeFormat.seconds = segons
|
||||
|
||||
fontNotFound = No s'ha trobat la tipografia amb id=%fontId%.
|
||||
|
||||
trait.scriptinitializer = iniciador del script
|
||||
trait.instanceinitializer = inicialitzador d'inst\u00e0ncia
|
||||
trait.classinitializer = inicialitzador de classe
|
||||
|
||||
@@ -34,3 +34,7 @@ timeFormat.second = vte\u0159ina
|
||||
timeFormat.seconds = vte\u0159in
|
||||
|
||||
fontNotFound = P\u00edsmo s id=%fontId% nebylo nalezeno.
|
||||
|
||||
trait.scriptinitializer = inicializ\u00e1tor skriptu
|
||||
trait.instanceinitializer = inicializ\u00e1tor instance
|
||||
trait.classinitializer = inicializ\u00e1tor t\u0159\u00eddy
|
||||
|
||||
@@ -13,3 +13,5 @@
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library.
|
||||
|
||||
trait.instanceinitializer = Instanzen-Initialisierer
|
||||
trait.classinitializer = Klassen-Initialisierer
|
||||
|
||||
@@ -34,3 +34,7 @@ timeFormat.second = segundo
|
||||
timeFormat.seconds = segundos
|
||||
|
||||
fontNotFound = La fuente con id=%fontId% no se encontr\u00f3.
|
||||
|
||||
trait.scriptinitializer = inicializador de script
|
||||
trait.instanceinitializer = inicializador de instancia
|
||||
trait.classinitializer = inicializador de clase
|
||||
|
||||
@@ -36,3 +36,7 @@ timeFormat.second = seconde
|
||||
timeFormat.seconds = secondes
|
||||
|
||||
fontNotFound = La police de caract\u00e8res n\u00ba%fontId% est introuvable.
|
||||
|
||||
trait.scriptinitializer = Initialisateur de script
|
||||
trait.instanceinitializer = instance d'initialisation
|
||||
trait.classinitializer = classe d'initialisation
|
||||
|
||||
@@ -36,3 +36,7 @@ timeFormat.second = m\u00e1sodperc
|
||||
timeFormat.seconds = m\u00e1sodperc
|
||||
|
||||
fontNotFound = Bet\u0171t\u00edpus (id=%fontId%) nem tal\u00e1lhat\u00f3.
|
||||
|
||||
trait.scriptinitializer = szkript inicializ\u00e1l\u00f3
|
||||
trait.instanceinitializer = p\u00e9ld\u00e1ny inicializ\u00e1l\u00f3
|
||||
trait.classinitializer = oszt\u00e1ly inicializ\u00e1l\u00f3
|
||||
|
||||
@@ -36,3 +36,7 @@ timeFormat.second = secondo
|
||||
timeFormat.seconds = secondi
|
||||
|
||||
fontNotFound = Font con id=%fontId% non trovato.
|
||||
|
||||
trait.scriptinitializer = Inizializzatore script
|
||||
trait.instanceinitializer = instance initializer
|
||||
trait.classinitializer = class initializer
|
||||
|
||||
@@ -34,3 +34,6 @@ timeFormat.second = seconde
|
||||
timeFormat.seconds = seconden
|
||||
|
||||
fontNotFound = Lettertype met id=%fontId% is niet gevonden.
|
||||
|
||||
trait.instanceinitializer = instantie initialiseerder
|
||||
trait.classinitializer = klasse initialiseerder
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Copyright (C) 2010-2016 JPEXS, All rights reserved.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library.
|
||||
|
||||
trait.instanceinitializer = inicjator instancji
|
||||
trait.classinitializer = inicjator klasy
|
||||
@@ -13,3 +13,5 @@
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library.
|
||||
|
||||
trait.instanceinitializer = inicializador de instancias
|
||||
trait.classinitializer = inicializador de classes
|
||||
|
||||
@@ -13,3 +13,5 @@
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library.
|
||||
|
||||
trait.instanceinitializer = inicializador de instancias
|
||||
trait.classinitializer = inicializador de classes
|
||||
|
||||
@@ -34,3 +34,6 @@ timeFormat.second = \u0441\u0435\u043a.
|
||||
timeFormat.seconds = \u0441\u0435\u043a.
|
||||
|
||||
fontNotFound = \u0428\u0440\u0438\u0444\u0442 \u0441 id=%fontId% \u043d\u0435 \u0431\u044b\u043b \u043d\u0430\u0439\u0434\u0435\u043d.
|
||||
|
||||
trait.instanceinitializer = \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430
|
||||
trait.classinitializer = \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440 \u043a\u043b\u0430\u0441\u0441\u0430
|
||||
|
||||
@@ -34,3 +34,6 @@ timeFormat.second = sekund
|
||||
timeFormat.seconds = sekunder
|
||||
|
||||
fontNotFound = Typsnitt med id=%fontId% hittades inte.
|
||||
|
||||
trait.instanceinitializer = instans initierare
|
||||
trait.classinitializer = klass initierare
|
||||
|
||||
@@ -34,3 +34,6 @@ timeFormat.second = \u0441\u0435\u043a.
|
||||
timeFormat.seconds = \u0441\u0435\u043a.
|
||||
|
||||
fontNotFound = \u0428\u0440\u0438\u0444\u0442 \u0437 id=%fontId% \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e.
|
||||
|
||||
trait.instanceinitializer = \u0456\u043d\u0456\u0446\u0456\u0430\u043b\u0456\u0437\u0430\u0442\u043e\u0440 \u043e\u0431'\u0454\u043a\u0442\u0430
|
||||
trait.classinitializer = \u0456\u043d\u0456\u0446\u0456\u0430\u043b\u0456\u0437\u0430\u0442\u043e\u0440 \u043a\u043b\u0430\u0441\u0443
|
||||
|
||||
@@ -13,3 +13,6 @@
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library.
|
||||
|
||||
trait.scriptinitializer = \u521d\u59cb\u5316\u811a\u672c
|
||||
trait.instanceinitializer = \u521d\u59cb\u5316\u5b9e\u4f8b
|
||||
trait.classinitializer = \u521d\u59cb\u5316\u7c7b
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.search;
|
||||
|
||||
import com.jpexs.decompiler.flash.AppResources;
|
||||
import com.jpexs.decompiler.flash.abc.ABC;
|
||||
import com.jpexs.decompiler.flash.abc.ScriptPack;
|
||||
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -24,38 +27,85 @@ import com.jpexs.decompiler.flash.abc.ScriptPack;
|
||||
*/
|
||||
public class ABCSearchResult {
|
||||
|
||||
public static String STR_INSTANCE_INITIALIZER = AppResources.translate("trait.instanceinitializer");
|
||||
|
||||
public static String STR_CLASS_INITIALIZER = AppResources.translate("trait.classinitializer");
|
||||
|
||||
public static String STR_SCRIPT_INITIALIZER = AppResources.translate("trait.scriptinitializer");
|
||||
|
||||
private final ScriptPack scriptPack;
|
||||
|
||||
private final boolean pcode;
|
||||
|
||||
private final int classIndex;
|
||||
|
||||
private final int methodIndex;
|
||||
private final int traitId;
|
||||
|
||||
public ABCSearchResult(ScriptPack scriptPack) {
|
||||
this.scriptPack = scriptPack;
|
||||
pcode = false;
|
||||
classIndex = 0;
|
||||
methodIndex = 0;
|
||||
traitId = GraphTextWriter.TRAIT_UNKNOWN;
|
||||
}
|
||||
|
||||
public ABCSearchResult(ScriptPack scriptPack, int classIndex, int methodIndex) {
|
||||
public ABCSearchResult(ScriptPack scriptPack, int classIndex, int traitId) {
|
||||
this.scriptPack = scriptPack;
|
||||
pcode = true;
|
||||
this.classIndex = classIndex;
|
||||
this.methodIndex = methodIndex;
|
||||
this.traitId = traitId;
|
||||
}
|
||||
|
||||
public ScriptPack getScriptPack() {
|
||||
return scriptPack;
|
||||
}
|
||||
|
||||
public int getClassIndex() {
|
||||
return classIndex;
|
||||
public boolean isPcode() {
|
||||
return pcode;
|
||||
}
|
||||
|
||||
public int getMethodIndex() {
|
||||
return methodIndex;
|
||||
public int getTraitId() {
|
||||
return traitId;
|
||||
}
|
||||
|
||||
private String getTraitName() {
|
||||
if (traitId == GraphTextWriter.TRAIT_SCRIPT_INITIALIZER) {
|
||||
return STR_SCRIPT_INITIALIZER;
|
||||
}
|
||||
|
||||
if (classIndex == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (traitId == GraphTextWriter.TRAIT_CLASS_INITIALIZER) {
|
||||
return STR_CLASS_INITIALIZER;
|
||||
}
|
||||
|
||||
if (traitId == GraphTextWriter.TRAIT_INSTANCE_INITIALIZER) {
|
||||
return STR_INSTANCE_INITIALIZER;
|
||||
}
|
||||
|
||||
ABC abc = scriptPack.abc;
|
||||
|
||||
int instanceTraitCount = abc.instance_info.get(classIndex).instance_traits.traits.size();
|
||||
boolean isStatic = traitId >= instanceTraitCount;
|
||||
|
||||
if (isStatic) {
|
||||
int index = traitId - instanceTraitCount;
|
||||
return abc.class_info.get(classIndex).static_traits.traits.get(index).getName(abc).getName(abc.constants, null, false, true);
|
||||
} else {
|
||||
return abc.instance_info.get(classIndex).instance_traits.traits.get(traitId).getName(abc).getName(abc.constants, null, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return scriptPack.getClassPath().toString();
|
||||
String result = scriptPack.getClassPath().toString();
|
||||
|
||||
if (pcode) {
|
||||
result += "/";
|
||||
result += getTraitName();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ public class ActionScriptSearch {
|
||||
abc.bodies.get(bodyIndex).getCode().toASMSource(abc.constants, abc.method_info.get(body.method_info), body, ScriptExportMode.PCODE, writer);
|
||||
String text = writer.toString();
|
||||
if (pat.matcher(text).find()) {
|
||||
ABCSearchResult searchResult = new ABCSearchResult(pack, methodInfo.getClassIndex(), methodInfo.getMethodIndex());
|
||||
ABCSearchResult searchResult = new ABCSearchResult(pack, methodInfo.getClassIndex(), methodInfo.getTraitId());
|
||||
found.add(searchResult);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,14 @@ package com.jpexs.decompiler.flash.search;
|
||||
*/
|
||||
public class MethodId {
|
||||
|
||||
private final int traitId;
|
||||
|
||||
private final int classIndex;
|
||||
|
||||
private final int methodIndex;
|
||||
|
||||
public MethodId(int classIndex, int methodIndex) {
|
||||
public MethodId(int traitId, int classIndex, int methodIndex) {
|
||||
this.traitId = traitId;
|
||||
this.classIndex = classIndex;
|
||||
this.methodIndex = methodIndex;
|
||||
}
|
||||
@@ -38,4 +41,8 @@ public class MethodId {
|
||||
public int getMethodIndex() {
|
||||
return methodIndex;
|
||||
}
|
||||
|
||||
public int getTraitId() {
|
||||
return traitId;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user