Initial commit
This commit is contained in:
663
engines/glk/glulx/accel.cpp
Normal file
663
engines/glk/glulx/accel.cpp
Normal file
@@ -0,0 +1,663 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "glk/glulx/glulx.h"
|
||||
|
||||
namespace Glk {
|
||||
namespace Glulx {
|
||||
|
||||
/**
|
||||
* Git passes along function arguments in reverse order. To make our lives more interesting
|
||||
*/
|
||||
#ifdef ARGS_REVERSED
|
||||
#define ARG(argv, argc, ix) (argv[(argc-1)-ix])
|
||||
#else
|
||||
#define ARG(argv, argc, ix) (argv[ix])
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Any function can be called with any number of arguments. This macro lets us snarf a given argument,
|
||||
* or zero if it wasn't supplied.
|
||||
*/
|
||||
#define ARG_IF_GIVEN(argv, argc, ix) ((argc > ix) ? (ARG(argv, argc, ix)) : 0)
|
||||
|
||||
acceleration_func Glulx::accel_find_func(uint index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return nullptr; // 0 always means no acceleration
|
||||
case 1:
|
||||
return &Glulx::func_1_z__region;
|
||||
case 2:
|
||||
return &Glulx::func_2_cp__tab;
|
||||
case 3:
|
||||
return &Glulx::func_3_ra__pr;
|
||||
case 4:
|
||||
return &Glulx::func_4_rl__pr;
|
||||
case 5:
|
||||
return &Glulx::func_5_oc__cl;
|
||||
case 6:
|
||||
return &Glulx::func_6_rv__pr;
|
||||
case 7:
|
||||
return &Glulx::func_7_op__pr;
|
||||
case 8:
|
||||
return &Glulx::func_8_cp__tab;
|
||||
case 9:
|
||||
return &Glulx::func_9_ra__pr;
|
||||
case 10:
|
||||
return &Glulx::func_10_rl__pr;
|
||||
case 11:
|
||||
return &Glulx::func_11_oc__cl;
|
||||
case 12:
|
||||
return &Glulx::func_12_rv__pr;
|
||||
case 13:
|
||||
return &Glulx::func_13_op__pr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
acceleration_func Glulx::accel_get_func(uint addr) {
|
||||
int bucknum;
|
||||
accelentry_t *ptr;
|
||||
|
||||
if (!accelentries)
|
||||
return nullptr;
|
||||
|
||||
bucknum = (addr % ACCEL_HASH_SIZE);
|
||||
for (ptr = accelentries[bucknum]; ptr; ptr = ptr->next) {
|
||||
if (ptr->addr == addr)
|
||||
return ptr->func;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Glulx::accel_iterate_funcs(void (*func)(uint index, uint addr)) {
|
||||
int bucknum;
|
||||
accelentry_t *ptr;
|
||||
|
||||
if (!accelentries)
|
||||
return;
|
||||
|
||||
for (bucknum = 0; bucknum < ACCEL_HASH_SIZE; bucknum++) {
|
||||
for (ptr = accelentries[bucknum]; ptr; ptr = ptr->next) {
|
||||
if (ptr->func) {
|
||||
func(ptr->index, ptr->addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Glulx::accel_set_func(uint index, uint addr) {
|
||||
int bucknum;
|
||||
accelentry_t *ptr;
|
||||
int functype;
|
||||
acceleration_func new_func = nullptr;
|
||||
|
||||
/* Check the Glulx type identifier byte. */
|
||||
functype = Mem1(addr);
|
||||
if (functype != 0xC0 && functype != 0xC1) {
|
||||
fatal_error_i("Attempt to accelerate non-function.", addr);
|
||||
}
|
||||
|
||||
if (!accelentries) {
|
||||
accelentries = (accelentry_t **)glulx_malloc(ACCEL_HASH_SIZE
|
||||
* sizeof(accelentry_t *));
|
||||
if (!accelentries)
|
||||
fatal_error("Cannot malloc acceleration table.");
|
||||
for (bucknum = 0; bucknum < ACCEL_HASH_SIZE; bucknum++)
|
||||
accelentries[bucknum] = nullptr;
|
||||
}
|
||||
|
||||
new_func = accel_find_func(index);
|
||||
/* Might be nullptr, if the index is zero or not recognized. */
|
||||
|
||||
bucknum = (addr % ACCEL_HASH_SIZE);
|
||||
for (ptr = accelentries[bucknum]; ptr; ptr = ptr->next) {
|
||||
if (ptr->addr == addr)
|
||||
break;
|
||||
}
|
||||
if (!ptr) {
|
||||
if (!new_func) {
|
||||
return; /* no need for a new entry */
|
||||
}
|
||||
ptr = (accelentry_t *)glulx_malloc(sizeof(accelentry_t));
|
||||
if (!ptr)
|
||||
fatal_error("Cannot malloc acceleration entry.");
|
||||
ptr->addr = addr;
|
||||
ptr->index = 0;
|
||||
ptr->func = nullptr;
|
||||
ptr->next = accelentries[bucknum];
|
||||
accelentries[bucknum] = ptr;
|
||||
}
|
||||
|
||||
ptr->index = index;
|
||||
ptr->func = new_func;
|
||||
}
|
||||
|
||||
void Glulx::accel_set_param(uint index, uint val) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
classes_table = val;
|
||||
break;
|
||||
case 1:
|
||||
indiv_prop_start = val;
|
||||
break;
|
||||
case 2:
|
||||
class_metaclass = val;
|
||||
break;
|
||||
case 3:
|
||||
object_metaclass = val;
|
||||
break;
|
||||
case 4:
|
||||
routine_metaclass = val;
|
||||
break;
|
||||
case 5:
|
||||
string_metaclass = val;
|
||||
break;
|
||||
case 6:
|
||||
self = val;
|
||||
break;
|
||||
case 7:
|
||||
num_attr_bytes = val;
|
||||
break;
|
||||
case 8:
|
||||
cpv__start = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint Glulx::accel_get_param_count() const {
|
||||
return 9;
|
||||
}
|
||||
|
||||
uint Glulx::accel_get_param(uint index) const {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return classes_table;
|
||||
case 1:
|
||||
return indiv_prop_start;
|
||||
case 2:
|
||||
return class_metaclass;
|
||||
case 3:
|
||||
return object_metaclass;
|
||||
case 4:
|
||||
return routine_metaclass;
|
||||
case 5:
|
||||
return string_metaclass;
|
||||
case 6:
|
||||
return self;
|
||||
case 7:
|
||||
return num_attr_bytes;
|
||||
case 8:
|
||||
return cpv__start;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Glulx::accel_error(const char *msg) {
|
||||
glk_put_char('\n');
|
||||
glk_put_string(msg);
|
||||
glk_put_char('\n');
|
||||
}
|
||||
|
||||
int Glulx::obj_in_class(uint obj) {
|
||||
// This checks whether obj is contained in Class, not whether it is a member of Class
|
||||
return (Mem4(obj + 13 + num_attr_bytes) == class_metaclass);
|
||||
}
|
||||
|
||||
uint Glulx::get_prop(uint obj, uint id) {
|
||||
uint cla = 0;
|
||||
uint prop;
|
||||
uint call_argv[2];
|
||||
|
||||
if (id & 0xFFFF0000) {
|
||||
cla = Mem4(classes_table + ((id & 0xFFFF) * 4));
|
||||
ARG(call_argv, 2, 0) = obj;
|
||||
ARG(call_argv, 2, 1) = cla;
|
||||
if (func_5_oc__cl(2, call_argv) == 0)
|
||||
return 0;
|
||||
|
||||
id >>= 16;
|
||||
obj = cla;
|
||||
}
|
||||
|
||||
ARG(call_argv, 2, 0) = obj;
|
||||
ARG(call_argv, 2, 1) = id;
|
||||
prop = func_2_cp__tab(2, call_argv);
|
||||
if (prop == 0)
|
||||
return 0;
|
||||
|
||||
if (obj_in_class(obj) && (cla == 0)) {
|
||||
if ((id < indiv_prop_start) || (id >= indiv_prop_start + 8))
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (Mem4(self) != obj) {
|
||||
if (Mem1(prop + 9) & 1)
|
||||
return 0;
|
||||
}
|
||||
return prop;
|
||||
}
|
||||
|
||||
uint Glulx::get_prop_new(uint obj, uint id) {
|
||||
uint cla = 0;
|
||||
uint prop;
|
||||
uint call_argv[2];
|
||||
|
||||
if (id & 0xFFFF0000) {
|
||||
cla = Mem4(classes_table + ((id & 0xFFFF) * 4));
|
||||
ARG(call_argv, 2, 0) = obj;
|
||||
ARG(call_argv, 2, 1) = cla;
|
||||
if (func_11_oc__cl(2, call_argv) == 0)
|
||||
return 0;
|
||||
|
||||
id >>= 16;
|
||||
obj = cla;
|
||||
}
|
||||
|
||||
ARG(call_argv, 2, 0) = obj;
|
||||
ARG(call_argv, 2, 1) = id;
|
||||
prop = func_8_cp__tab(2, call_argv);
|
||||
if (prop == 0)
|
||||
return 0;
|
||||
|
||||
if (obj_in_class(obj) && (cla == 0)) {
|
||||
if ((id < indiv_prop_start) || (id >= indiv_prop_start + 8))
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (Mem4(self) != obj) {
|
||||
if (Mem1(prop + 9) & 1)
|
||||
return 0;
|
||||
}
|
||||
return prop;
|
||||
}
|
||||
|
||||
uint Glulx::func_1_z__region(uint argc, uint *argv) {
|
||||
uint addr;
|
||||
uint tb;
|
||||
|
||||
if (argc < 1)
|
||||
return 0;
|
||||
|
||||
addr = ARG(argv, argc, 0);
|
||||
if (addr < 36)
|
||||
return 0;
|
||||
if (addr >= endmem)
|
||||
return 0;
|
||||
|
||||
tb = Mem1(addr);
|
||||
if (tb >= 0xE0) {
|
||||
return 3;
|
||||
}
|
||||
if (tb >= 0xC0) {
|
||||
return 2;
|
||||
}
|
||||
if (tb >= 0x70 && tb <= 0x7F && addr >= ramstart) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint Glulx::func_2_cp__tab(uint argc, uint *argv) {
|
||||
uint obj;
|
||||
uint id;
|
||||
uint otab, max;
|
||||
|
||||
obj = ARG_IF_GIVEN(argv, argc, 0);
|
||||
id = ARG_IF_GIVEN(argv, argc, 1);
|
||||
|
||||
if (func_1_z__region(1, &obj) != 1) {
|
||||
accel_error("[** Programming error: tried to find the \".\" of (something) **]");
|
||||
return 0;
|
||||
}
|
||||
|
||||
otab = Mem4(obj + 16);
|
||||
if (!otab)
|
||||
return 0;
|
||||
|
||||
max = Mem4(otab);
|
||||
otab += 4;
|
||||
/* @binarysearch id 2 otab 10 max 0 0 res; */
|
||||
return binary_search(id, 2, otab, 10, max, 0, 0);
|
||||
}
|
||||
|
||||
uint Glulx::func_3_ra__pr(uint argc, uint *argv) {
|
||||
uint obj;
|
||||
uint id;
|
||||
uint prop;
|
||||
|
||||
obj = ARG_IF_GIVEN(argv, argc, 0);
|
||||
id = ARG_IF_GIVEN(argv, argc, 1);
|
||||
|
||||
prop = get_prop(obj, id);
|
||||
if (prop == 0)
|
||||
return 0;
|
||||
|
||||
return Mem4(prop + 4);
|
||||
}
|
||||
|
||||
uint Glulx::func_4_rl__pr(uint argc, uint *argv) {
|
||||
uint obj;
|
||||
uint id;
|
||||
uint prop;
|
||||
|
||||
obj = ARG_IF_GIVEN(argv, argc, 0);
|
||||
id = ARG_IF_GIVEN(argv, argc, 1);
|
||||
|
||||
prop = get_prop(obj, id);
|
||||
if (prop == 0)
|
||||
return 0;
|
||||
|
||||
return 4 * Mem2(prop + 2);
|
||||
}
|
||||
|
||||
uint Glulx::func_5_oc__cl(uint argc, uint *argv) {
|
||||
uint obj;
|
||||
uint cla;
|
||||
uint zr, prop, inlist, inlistlen, jx;
|
||||
|
||||
obj = ARG_IF_GIVEN(argv, argc, 0);
|
||||
cla = ARG_IF_GIVEN(argv, argc, 1);
|
||||
|
||||
zr = func_1_z__region(1, &obj);
|
||||
if (zr == 3)
|
||||
return (cla == string_metaclass) ? 1 : 0;
|
||||
if (zr == 2)
|
||||
return (cla == routine_metaclass) ? 1 : 0;
|
||||
if (zr != 1)
|
||||
return 0;
|
||||
|
||||
if (cla == class_metaclass) {
|
||||
if (obj_in_class(obj))
|
||||
return 1;
|
||||
if (obj == class_metaclass)
|
||||
return 1;
|
||||
if (obj == string_metaclass)
|
||||
return 1;
|
||||
if (obj == routine_metaclass)
|
||||
return 1;
|
||||
if (obj == object_metaclass)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
if (cla == object_metaclass) {
|
||||
if (obj_in_class(obj))
|
||||
return 0;
|
||||
if (obj == class_metaclass)
|
||||
return 0;
|
||||
if (obj == string_metaclass)
|
||||
return 0;
|
||||
if (obj == routine_metaclass)
|
||||
return 0;
|
||||
if (obj == object_metaclass)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
if ((cla == string_metaclass) || (cla == routine_metaclass))
|
||||
return 0;
|
||||
|
||||
if (!obj_in_class(cla)) {
|
||||
accel_error("[** Programming error: tried to apply 'ofclass' with non-class **]");
|
||||
return 0;
|
||||
}
|
||||
|
||||
prop = get_prop(obj, 2);
|
||||
if (prop == 0)
|
||||
return 0;
|
||||
|
||||
inlist = Mem4(prop + 4);
|
||||
if (inlist == 0)
|
||||
return 0;
|
||||
|
||||
inlistlen = Mem2(prop + 2);
|
||||
for (jx = 0; jx < inlistlen; jx++) {
|
||||
if (Mem4(inlist + (4 * jx)) == cla)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint Glulx::func_6_rv__pr(uint argc, uint *argv) {
|
||||
uint id;
|
||||
uint addr;
|
||||
|
||||
id = ARG_IF_GIVEN(argv, argc, 1);
|
||||
|
||||
addr = func_3_ra__pr(argc, argv);
|
||||
|
||||
if (addr == 0) {
|
||||
if ((id > 0) && (id < indiv_prop_start))
|
||||
return Mem4(cpv__start + (4 * id));
|
||||
|
||||
accel_error("[** Programming error: tried to read (something) **]");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Mem4(addr);
|
||||
}
|
||||
|
||||
uint Glulx::func_7_op__pr(uint argc, uint *argv) {
|
||||
uint obj;
|
||||
uint id;
|
||||
uint zr;
|
||||
|
||||
obj = ARG_IF_GIVEN(argv, argc, 0);
|
||||
id = ARG_IF_GIVEN(argv, argc, 1);
|
||||
|
||||
zr = func_1_z__region(1, &obj);
|
||||
if (zr == 3) {
|
||||
/* print is INDIV_PROP_START+6 */
|
||||
if (id == indiv_prop_start + 6)
|
||||
return 1;
|
||||
/* print_to_array is INDIV_PROP_START+7 */
|
||||
if (id == indiv_prop_start + 7)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
if (zr == 2) {
|
||||
/* call is INDIV_PROP_START+5 */
|
||||
return ((id == indiv_prop_start + 5) ? 1 : 0);
|
||||
}
|
||||
if (zr != 1)
|
||||
return 0;
|
||||
|
||||
if ((id >= indiv_prop_start) && (id < indiv_prop_start + 8)) {
|
||||
if (obj_in_class(obj))
|
||||
return 1;
|
||||
}
|
||||
|
||||
return ((func_3_ra__pr(argc, argv)) ? 1 : 0);
|
||||
}
|
||||
|
||||
uint Glulx::func_8_cp__tab(uint argc, uint *argv) {
|
||||
uint obj;
|
||||
uint id;
|
||||
uint otab, max;
|
||||
|
||||
obj = ARG_IF_GIVEN(argv, argc, 0);
|
||||
id = ARG_IF_GIVEN(argv, argc, 1);
|
||||
|
||||
if (func_1_z__region(1, &obj) != 1) {
|
||||
accel_error("[** Programming error: tried to find the \".\" of (something) **]");
|
||||
return 0;
|
||||
}
|
||||
|
||||
otab = Mem4(obj + 4 * (3 + (int)(num_attr_bytes / 4)));
|
||||
if (!otab)
|
||||
return 0;
|
||||
|
||||
max = Mem4(otab);
|
||||
otab += 4;
|
||||
/* @binarysearch id 2 otab 10 max 0 0 res; */
|
||||
return binary_search(id, 2, otab, 10, max, 0, 0);
|
||||
}
|
||||
|
||||
uint Glulx::func_9_ra__pr(uint argc, uint *argv) {
|
||||
uint obj;
|
||||
uint id;
|
||||
uint prop;
|
||||
|
||||
obj = ARG_IF_GIVEN(argv, argc, 0);
|
||||
id = ARG_IF_GIVEN(argv, argc, 1);
|
||||
|
||||
prop = get_prop_new(obj, id);
|
||||
if (prop == 0)
|
||||
return 0;
|
||||
|
||||
return Mem4(prop + 4);
|
||||
}
|
||||
|
||||
uint Glulx::func_10_rl__pr(uint argc, uint *argv) {
|
||||
uint obj;
|
||||
uint id;
|
||||
uint prop;
|
||||
|
||||
obj = ARG_IF_GIVEN(argv, argc, 0);
|
||||
id = ARG_IF_GIVEN(argv, argc, 1);
|
||||
|
||||
prop = get_prop_new(obj, id);
|
||||
if (prop == 0)
|
||||
return 0;
|
||||
|
||||
return 4 * Mem2(prop + 2);
|
||||
}
|
||||
|
||||
uint Glulx::func_11_oc__cl(uint argc, uint *argv) {
|
||||
uint obj;
|
||||
uint cla;
|
||||
uint zr, prop, inlist, inlistlen, jx;
|
||||
|
||||
obj = ARG_IF_GIVEN(argv, argc, 0);
|
||||
cla = ARG_IF_GIVEN(argv, argc, 1);
|
||||
|
||||
zr = func_1_z__region(1, &obj);
|
||||
if (zr == 3)
|
||||
return (cla == string_metaclass) ? 1 : 0;
|
||||
if (zr == 2)
|
||||
return (cla == routine_metaclass) ? 1 : 0;
|
||||
if (zr != 1)
|
||||
return 0;
|
||||
|
||||
if (cla == class_metaclass) {
|
||||
if (obj_in_class(obj))
|
||||
return 1;
|
||||
if (obj == class_metaclass)
|
||||
return 1;
|
||||
if (obj == string_metaclass)
|
||||
return 1;
|
||||
if (obj == routine_metaclass)
|
||||
return 1;
|
||||
if (obj == object_metaclass)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
if (cla == object_metaclass) {
|
||||
if (obj_in_class(obj))
|
||||
return 0;
|
||||
if (obj == class_metaclass)
|
||||
return 0;
|
||||
if (obj == string_metaclass)
|
||||
return 0;
|
||||
if (obj == routine_metaclass)
|
||||
return 0;
|
||||
if (obj == object_metaclass)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
if ((cla == string_metaclass) || (cla == routine_metaclass))
|
||||
return 0;
|
||||
|
||||
if (!obj_in_class(cla)) {
|
||||
accel_error("[** Programming error: tried to apply 'ofclass' with non-class **]");
|
||||
return 0;
|
||||
}
|
||||
|
||||
prop = get_prop_new(obj, 2);
|
||||
if (prop == 0)
|
||||
return 0;
|
||||
|
||||
inlist = Mem4(prop + 4);
|
||||
if (inlist == 0)
|
||||
return 0;
|
||||
|
||||
inlistlen = Mem2(prop + 2);
|
||||
for (jx = 0; jx < inlistlen; jx++) {
|
||||
if (Mem4(inlist + (4 * jx)) == cla)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint Glulx::func_12_rv__pr(uint argc, uint *argv) {
|
||||
uint id;
|
||||
uint addr;
|
||||
|
||||
id = ARG_IF_GIVEN(argv, argc, 1);
|
||||
|
||||
addr = func_9_ra__pr(argc, argv);
|
||||
|
||||
if (addr == 0) {
|
||||
if ((id > 0) && (id < indiv_prop_start))
|
||||
return Mem4(cpv__start + (4 * id));
|
||||
|
||||
accel_error("[** Programming error: tried to read (something) **]");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Mem4(addr);
|
||||
}
|
||||
|
||||
uint Glulx::func_13_op__pr(uint argc, uint *argv) {
|
||||
uint obj;
|
||||
uint id;
|
||||
uint zr;
|
||||
|
||||
obj = ARG_IF_GIVEN(argv, argc, 0);
|
||||
id = ARG_IF_GIVEN(argv, argc, 1);
|
||||
|
||||
zr = func_1_z__region(1, &obj);
|
||||
if (zr == 3) {
|
||||
/* print is INDIV_PROP_START+6 */
|
||||
if (id == indiv_prop_start + 6)
|
||||
return 1;
|
||||
/* print_to_array is INDIV_PROP_START+7 */
|
||||
if (id == indiv_prop_start + 7)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
if (zr == 2) {
|
||||
/* call is INDIV_PROP_START+5 */
|
||||
return ((id == indiv_prop_start + 5) ? 1 : 0);
|
||||
}
|
||||
if (zr != 1)
|
||||
return 0;
|
||||
|
||||
if ((id >= indiv_prop_start) && (id < indiv_prop_start + 8)) {
|
||||
if (obj_in_class(obj))
|
||||
return 1;
|
||||
}
|
||||
|
||||
return ((func_9_ra__pr(argc, argv)) ? 1 : 0);
|
||||
}
|
||||
|
||||
} // End of namespace Glulx
|
||||
} // End of namespace Glk
|
||||
Reference in New Issue
Block a user