update to ida 7.6, add builds
This commit is contained in:
316
idasdk76/plugins/formchooser/formchooser.cpp
Normal file
316
idasdk76/plugins/formchooser/formchooser.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* This plugin demonstrates how to use choosers inside forms.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ida.hpp>
|
||||
#include <idp.hpp>
|
||||
#include <loader.hpp>
|
||||
#include <kernwin.hpp>
|
||||
|
||||
#define ACTION_NAME "formchooser:action"
|
||||
#define TITLE_PFX "Form with choosers"
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// raw data of the png icon (16x16)
|
||||
static const unsigned char icon_data[182] =
|
||||
{
|
||||
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
|
||||
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF,
|
||||
0x61, 0x00, 0x00, 0x00, 0x7D, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0x63, 0x64, 0xC0, 0x0E, 0xFE,
|
||||
0xE3, 0x10, 0x67, 0x24, 0x28, 0x00, 0xD2, 0xFC, 0xF3, 0xAF, 0x36, 0x56, 0xDD, 0xEC, 0xCC, 0x57,
|
||||
0x31, 0xF4, 0x20, 0x73, 0xC0, 0xB6, 0xE2, 0xD2, 0x8C, 0x66, 0x08, 0x5C, 0x2F, 0x8A, 0x01, 0x84,
|
||||
0x34, 0x63, 0x73, 0x09, 0x23, 0xA9, 0x9A, 0xD1, 0x0D, 0x61, 0x44, 0xD7, 0xCC, 0xCF, 0x02, 0x71,
|
||||
0xE2, 0xC7, 0x3F, 0xA8, 0x06, 0x62, 0x13, 0x07, 0x19, 0x42, 0x7D, 0x03, 0x48, 0xF5, 0xC6, 0x20,
|
||||
0x34, 0x00, 0xE4, 0x57, 0x74, 0xFF, 0xE3, 0x92, 0x83, 0x19, 0xC0, 0x40, 0x8C, 0x21, 0xD8, 0x34,
|
||||
0x33, 0x40, 0xA3, 0x91, 0x01, 0x97, 0x21, 0xC8, 0x00, 0x9B, 0x66, 0x38, 0x01, 0x33, 0x00, 0x44,
|
||||
0x50, 0x92, 0x94, 0xB1, 0xBA, 0x04, 0x8B, 0x66, 0x9C, 0x99, 0x09, 0xC5, 0x10, 0x1C, 0xE2, 0x18,
|
||||
0xEA, 0x01, 0xA3, 0x65, 0x55, 0x0B, 0x33, 0x14, 0x07, 0x63, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
|
||||
0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
struct formchooser_ah_t : public action_handler_t
|
||||
{
|
||||
virtual int idaapi activate(action_activation_ctx_t *ctx) override
|
||||
{
|
||||
msg("Menu item clicked. Current selection:");
|
||||
for ( size_t i = 0, n = ctx->chooser_selection.size(); i < n; ++i )
|
||||
msg(" %" FMT_Z, ctx->chooser_selection[i]);
|
||||
msg("\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
virtual action_state_t idaapi update(action_update_ctx_t *ctx) override
|
||||
{
|
||||
bool ok = ctx->widget_type == BWN_CHOOSER;
|
||||
if ( ok )
|
||||
{
|
||||
qstring name;
|
||||
ok = get_widget_title(&name, ctx->widget)
|
||||
&& strneq(name.c_str(), TITLE_PFX, qstrlen(TITLE_PFX));
|
||||
}
|
||||
return ok ? AST_ENABLE_FOR_WIDGET : AST_DISABLE_FOR_WIDGET;
|
||||
}
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//lint -e{958} padding needed
|
||||
struct plugin_ctx_t : public plugmod_t
|
||||
{
|
||||
int icon_id = load_custom_icon(icon_data, sizeof(icon_data), "png");
|
||||
|
||||
formchooser_ah_t formchooser_ah;
|
||||
const action_desc_t formchooser_desc = ACTION_DESC_LITERAL_PLUGMOD(
|
||||
ACTION_NAME,
|
||||
"Test",
|
||||
&formchooser_ah,
|
||||
this,
|
||||
"Ctrl-K",
|
||||
nullptr,
|
||||
icon_id);
|
||||
|
||||
int main_current_index = -1;
|
||||
|
||||
virtual bool idaapi run(size_t) override;
|
||||
|
||||
static int idaapi modcb(int fid, form_actions_t &fa);
|
||||
void refresh_selection_edit(form_actions_t & fa);
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
struct mainch_chooser_t : public chooser_t
|
||||
{
|
||||
protected:
|
||||
static const int widths_[];
|
||||
static const char *const header_[];
|
||||
friend struct auxch_chooser_t;
|
||||
|
||||
public:
|
||||
// this chooser is embedded into the modal form
|
||||
inline mainch_chooser_t(int icon_id);
|
||||
|
||||
virtual size_t idaapi get_count() const override { return 10; }
|
||||
virtual void idaapi get_row(
|
||||
qstrvec_t *cols,
|
||||
int *icon_,
|
||||
chooser_item_attrs_t *attrs,
|
||||
size_t n) const override;
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
struct auxch_chooser_t : public chooser_multi_t
|
||||
{
|
||||
public:
|
||||
plugin_ctx_t &ctx;
|
||||
|
||||
// this chooser is embedded into the modal form
|
||||
auxch_chooser_t(plugin_ctx_t &ctx, int icon_id);
|
||||
|
||||
virtual size_t idaapi get_count() const override
|
||||
{
|
||||
return ctx.main_current_index + 1;
|
||||
}
|
||||
virtual void idaapi get_row(
|
||||
qstrvec_t *cols,
|
||||
int *icon_,
|
||||
chooser_item_attrs_t *attrs,
|
||||
size_t n) const override;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
const int mainch_chooser_t::widths_[] = { 40 };
|
||||
const char *const mainch_chooser_t::header_[] = { "Item" };
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
inline mainch_chooser_t::mainch_chooser_t(int icon_)
|
||||
: chooser_t(CH_KEEP | CH_NOIDB,
|
||||
qnumber(widths_), widths_, header_)
|
||||
{
|
||||
CASSERT(qnumber(widths_) == qnumber(header_));
|
||||
icon = icon_;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
void idaapi mainch_chooser_t::get_row(
|
||||
qstrvec_t *cols_,
|
||||
int *,
|
||||
chooser_item_attrs_t *,
|
||||
size_t n) const
|
||||
{
|
||||
qstrvec_t &cols = *cols_;
|
||||
cols[0].sprnt("Option %" FMT_Z, n + 1);
|
||||
CASSERT(qnumber(header_) == 1);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
auxch_chooser_t::auxch_chooser_t(plugin_ctx_t &ctx_, int icon_)
|
||||
: chooser_multi_t(
|
||||
CH_KEEP | CH_NOIDB,
|
||||
qnumber(mainch_chooser_t::widths_),
|
||||
mainch_chooser_t::widths_,
|
||||
mainch_chooser_t::header_),
|
||||
ctx(ctx_)
|
||||
{
|
||||
icon = icon_;
|
||||
}
|
||||
//-------------------------------------------------------------------------
|
||||
void idaapi auxch_chooser_t::get_row(
|
||||
qstrvec_t *cols_,
|
||||
int *,
|
||||
chooser_item_attrs_t *,
|
||||
size_t n) const
|
||||
{
|
||||
qstrvec_t &cols = *cols_;
|
||||
cols[0].sprnt("Item %" FMT_Z, n + 1);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
void plugin_ctx_t::refresh_selection_edit(form_actions_t & fa)
|
||||
{
|
||||
qstring str;
|
||||
if ( main_current_index == -1 )
|
||||
{
|
||||
str = "No selection";
|
||||
}
|
||||
else
|
||||
{
|
||||
str.sprnt("Main %d", main_current_index + 1);
|
||||
|
||||
sizevec_t array;
|
||||
fa.get_chooser_value(4, &array);
|
||||
if ( array.size() > 0 )
|
||||
{
|
||||
str.append(" - Aux item(s) ");
|
||||
for ( int i = 0; i < array.size(); i++ )
|
||||
{
|
||||
if ( i != 0 )
|
||||
str.append(", ");
|
||||
str.cat_sprnt("%" FMT_Z, array[i] + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
fa.set_string_value(5, &str);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
int idaapi plugin_ctx_t::modcb(int fid, form_actions_t &fa)
|
||||
{
|
||||
plugin_ctx_t &ctx = *(plugin_ctx_t *)fa.get_ud();
|
||||
switch ( fid )
|
||||
{
|
||||
case CB_INIT:
|
||||
msg("initializing\n");
|
||||
ctx.refresh_selection_edit(fa);
|
||||
break;
|
||||
case CB_YES:
|
||||
msg("terminating\n");
|
||||
break;
|
||||
// main chooser
|
||||
case 3:
|
||||
{
|
||||
msg("main chooser selection change\n");
|
||||
sizevec_t array;
|
||||
fa.get_chooser_value(3, &array);
|
||||
ctx.main_current_index = !array.empty() ? array[0] : -1;
|
||||
// refresh auxiliar chooser
|
||||
fa.refresh_field(4);
|
||||
ctx.refresh_selection_edit(fa);
|
||||
}
|
||||
break;
|
||||
// auxiliar chooser
|
||||
case 4:
|
||||
ctx.refresh_selection_edit(fa);
|
||||
break;
|
||||
// Aux value text control
|
||||
case 5:
|
||||
break;
|
||||
default:
|
||||
msg("unknown id %d\n", fid);
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
bool idaapi plugin_ctx_t::run(size_t)
|
||||
{
|
||||
struct ida_local lambda_t
|
||||
{
|
||||
static ssize_t idaapi cb(void *, int code, va_list va)
|
||||
{
|
||||
if ( code == ui_finish_populating_widget_popup )
|
||||
{
|
||||
TWidget *widget = va_arg(va, TWidget *);
|
||||
TPopupMenu *popup_handle = va_arg(va, TPopupMenu *);
|
||||
// Let the chooser populate itself normally first.
|
||||
// We'll add our own stuff on second pass.
|
||||
qstring buf;
|
||||
if ( get_widget_type(widget) == BWN_CHOOSER
|
||||
&& get_widget_title(&buf, widget)
|
||||
&& buf == TITLE_PFX":3" )
|
||||
{
|
||||
attach_action_to_popup(widget, popup_handle, ACTION_NAME);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
hook_to_notification_point(HT_UI, lambda_t::cb);
|
||||
|
||||
static const char form[] =
|
||||
"STARTITEM 0\n"
|
||||
TITLE_PFX"\n\n"
|
||||
"%/%*"
|
||||
"Select an item in the main chooser:\n"
|
||||
"\n"
|
||||
"<Main chooser:E3::30::><Auxiliar chooser (multi):E4::30::>\n\n"
|
||||
"<Selection:q5:1023:40::>\n"
|
||||
"\n";
|
||||
|
||||
register_action(formchooser_desc);
|
||||
|
||||
mainch_chooser_t main_ch(icon_id);
|
||||
sizevec_t main_sel; // no selection by default
|
||||
main_current_index = -1;
|
||||
|
||||
auxch_chooser_t aux_ch(*this, icon_id);
|
||||
sizevec_t aux_sel; // no selection by default
|
||||
|
||||
qstring str;
|
||||
|
||||
CASSERT(IS_CHOOSER_BASE_T(main_ch));
|
||||
CASSERT(IS_CHOOSER_BASE_T(aux_ch));
|
||||
if ( ask_form(form, modcb, this,
|
||||
&main_ch, &main_sel,
|
||||
&aux_ch, &aux_sel,
|
||||
&str) > 0 )
|
||||
{
|
||||
msg("Selection: %s\n", str.c_str());
|
||||
}
|
||||
|
||||
unhook_from_notification_point(HT_UI, lambda_t::cb);
|
||||
return true;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
static plugmod_t *idaapi init()
|
||||
{
|
||||
return new plugin_ctx_t;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
plugin_t PLUGIN =
|
||||
{
|
||||
IDP_INTERFACE_VERSION,
|
||||
PLUGIN_UNL // Unload the plugin immediately after calling 'run'
|
||||
| PLUGIN_MULTI, // The plugin can work with multiple idbs in parallel
|
||||
init,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
"Forms chooser sample",// the preferred short name of the plugin
|
||||
nullptr,
|
||||
};
|
||||
204
idasdk76/plugins/formchooser/formchooser.py
Normal file
204
idasdk76/plugins/formchooser/formchooser.py
Normal file
@@ -0,0 +1,204 @@
|
||||
import idaapi
|
||||
from ida_kernwin import Choose, Form
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
class MainChooserClass(Choose):
|
||||
def __init__(self, title, icon):
|
||||
Choose.__init__(self,
|
||||
title,
|
||||
[ ["Item", 10] ],
|
||||
icon=icon,
|
||||
flags=Choose.CH_NOIDB,
|
||||
embedded=True, width=30, height=20)
|
||||
|
||||
def OnGetLine(self, n):
|
||||
return ["Option %d" % (n + 1)]
|
||||
|
||||
def OnGetSize(self):
|
||||
return 10
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
class AuxChooserClass(Choose):
|
||||
def __init__(self, title, icon):
|
||||
Choose.__init__(self,
|
||||
title,
|
||||
[ ["Item", 10] ],
|
||||
icon=icon,
|
||||
flags=Choose.CH_NOIDB | Choose.CH_MULTI,
|
||||
embedded=True, width=30, height=20)
|
||||
|
||||
def OnGetLine(self, n):
|
||||
return ["Item %d" % (n + 1)]
|
||||
|
||||
def OnGetSize(self):
|
||||
return self.form.main_current_index + 1
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
class TestActionHandler(idaapi.action_handler_t):
|
||||
def __init__(self, chooser):
|
||||
idaapi.action_handler_t.__init__(self)
|
||||
self.chooser = chooser
|
||||
|
||||
def activate(self, ctx):
|
||||
sel = []
|
||||
for idx in ctx.chooser_selection:
|
||||
sel.append(str(idx))
|
||||
print("Menu item clicked. Current selection: %s" % sel);
|
||||
|
||||
def update(self, ctx):
|
||||
return idaapi.AST_ENABLE_ALWAYS
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
class MyChooserForm(Form):
|
||||
|
||||
# Custom icon data
|
||||
icon_data = (
|
||||
b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52"
|
||||
b"\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1F\xF3\xFF"
|
||||
b"\x61\x00\x00\x00\x7D\x49\x44\x41\x54\x78\xDA\x63\x64\xC0\x0E\xFE"
|
||||
b"\xE3\x10\x67\x24\x28\x00\xD2\xFC\xF3\xAF\x36\x56\xDD\xEC\xCC\x57"
|
||||
b"\x31\xF4\x20\x73\xC0\xB6\xE2\xD2\x8C\x66\x08\x5C\x2F\x8A\x01\x84"
|
||||
b"\x34\x63\x73\x09\x23\xA9\x9A\xD1\x0D\x61\x44\xD7\xCC\xCF\x02\x71"
|
||||
b"\xE2\xC7\x3F\xA8\x06\x62\x13\x07\x19\x42\x7D\x03\x48\xF5\xC6\x20"
|
||||
b"\x34\x00\xE4\x57\x74\xFF\xE3\x92\x83\x19\xC0\x40\x8C\x21\xD8\x34"
|
||||
b"\x33\x40\xA3\x91\x01\x97\x21\xC8\x00\x9B\x66\x38\x01\x33\x00\x44"
|
||||
b"\x50\x92\x94\xB1\xBA\x04\x8B\x66\x9C\x99\x09\xC5\x10\x1C\xE2\x18"
|
||||
b"\xEA\x01\xA3\x65\x55\x0B\x33\x14\x07\x63\x00\x00\x00\x00\x49\x45"
|
||||
b"\x4E\x44\xAE\x42\x60\x82")
|
||||
|
||||
TITLE_PFX = "Form with choosers"
|
||||
|
||||
|
||||
def Free(self):
|
||||
self.hooks.unhook()
|
||||
self.hooks = None
|
||||
|
||||
# Call the base
|
||||
Form.Free(self)
|
||||
|
||||
# Free icon
|
||||
if self.icon_id != 0:
|
||||
idaapi.free_custom_icon(self.icon_id)
|
||||
self.icon_id = 0
|
||||
|
||||
# Remove local bindings
|
||||
self.EChMain = None
|
||||
self.EChAux = None
|
||||
|
||||
|
||||
def __init__(self):
|
||||
# Load custom icon
|
||||
self.icon_id = idaapi.load_custom_icon(data=MyChooserForm.icon_data)
|
||||
if self.icon_id == 0:
|
||||
raise RuntimeError("Failed to load icon data!")
|
||||
|
||||
self.main_current_index = -1
|
||||
self.EChMain = MainChooserClass("MainChooser", self.icon_id)
|
||||
self.EChAux = AuxChooserClass("AuxChooser", self.icon_id)
|
||||
|
||||
# Link the form to the EChooser
|
||||
self.EChMain.form = self
|
||||
self.EChAux.form = self
|
||||
|
||||
Form.__init__(self, r"""STARTITEM 0
|
||||
%s
|
||||
|
||||
{FormChangeCb}
|
||||
Select an item in the main chooser:
|
||||
|
||||
<Main chooser:{ctrlMainChooser}><Auxiliar chooser (multi):{ctrlAuxChooser}>
|
||||
|
||||
|
||||
<Selection:{ctrlSelectionEdit}>
|
||||
|
||||
""" % self.TITLE_PFX, {
|
||||
'ctrlSelectionEdit' : Form.StringInput(),
|
||||
'FormChangeCb' : Form.FormChangeCb(self.OnFormChange),
|
||||
'ctrlMainChooser' : Form.EmbeddedChooserControl(self.EChMain),
|
||||
'ctrlAuxChooser' : Form.EmbeddedChooserControl(self.EChAux),
|
||||
})
|
||||
|
||||
# Add an action to the popup menu of the main chooser
|
||||
class Hooks(idaapi.UI_Hooks):
|
||||
def __init__(self, form):
|
||||
idaapi.UI_Hooks.__init__(self)
|
||||
self.form = form
|
||||
|
||||
def finish_populating_widget_popup(self, widget, popup):
|
||||
# ids are assigned alphabetically by name
|
||||
# so the id of the MainChooser is 3
|
||||
if idaapi.get_widget_type(widget) == idaapi.BWN_CHOOSER \
|
||||
and idaapi.get_widget_title(widget) == \
|
||||
MyChooserForm.TITLE_PFX + ":3":
|
||||
actdesc = idaapi.action_desc_t(
|
||||
None,
|
||||
"Test",
|
||||
TestActionHandler(self.form.EChMain),
|
||||
"Ctrl-K", # shortcut
|
||||
None, # tooltip
|
||||
self.form.icon_id)
|
||||
idaapi.attach_dynamic_action_to_popup(
|
||||
widget,
|
||||
popup,
|
||||
actdesc)
|
||||
self.hooks = Hooks(self)
|
||||
self.hooks.hook()
|
||||
|
||||
|
||||
def refresh_selection_edit(self):
|
||||
if self.main_current_index == -1:
|
||||
s = "No selection in the main chooser"
|
||||
else:
|
||||
s = "Main %d" % (self.main_current_index + 1)
|
||||
|
||||
# Get selection in the aux chooser
|
||||
sel = self.GetControlValue(self.ctrlAuxChooser)
|
||||
if sel:
|
||||
s = "%s - Aux item(s): %s" % (s, ",".join(str(x + 1) for x in sel))
|
||||
|
||||
# Update string input
|
||||
self.SetControlValue(self.ctrlSelectionEdit, s)
|
||||
|
||||
|
||||
def OnFormChange(self, fid):
|
||||
if fid == -1:
|
||||
print("initializing")
|
||||
self.refresh_selection_edit()
|
||||
|
||||
elif fid == -2:
|
||||
print("terminating");
|
||||
|
||||
elif fid == self.ctrlMainChooser.id:
|
||||
print("main chooser selection change");
|
||||
l = self.GetControlValue(self.ctrlMainChooser);
|
||||
self.main_current_index = l[0] if l else -1
|
||||
|
||||
# Refresh auxiliar chooser
|
||||
self.RefreshField(self.ctrlAuxChooser)
|
||||
self.refresh_selection_edit()
|
||||
|
||||
elif fid == self.ctrlAuxChooser.id:
|
||||
self.refresh_selection_edit()
|
||||
|
||||
elif fid == self.ctrlSelectionEdit.id:
|
||||
pass
|
||||
else:
|
||||
print("unknown id %d" % fid)
|
||||
|
||||
return 1
|
||||
|
||||
def main():
|
||||
global f
|
||||
f = MyChooserForm()
|
||||
try:
|
||||
f.Compile()
|
||||
r = f.Execute()
|
||||
print("Execute returned: %d" % r)
|
||||
f.Free()
|
||||
except Exception as e:
|
||||
print("Failed to show form: %s" % str(e))
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
||||
11
idasdk76/plugins/formchooser/makefile
Normal file
11
idasdk76/plugins/formchooser/makefile
Normal file
@@ -0,0 +1,11 @@
|
||||
PROC=formchooser
|
||||
|
||||
include ../plugin.mak
|
||||
|
||||
# MAKEDEP dependency list ------------------
|
||||
$(F)formchooser$(O): $(I)bitrange.hpp $(I)bytes.hpp $(I)config.hpp \
|
||||
$(I)fpro.h $(I)funcs.hpp $(I)ida.hpp $(I)idp.hpp \
|
||||
$(I)ieee.h $(I)kernwin.hpp $(I)lines.hpp $(I)llong.hpp \
|
||||
$(I)loader.hpp $(I)nalt.hpp $(I)netnode.hpp $(I)pro.h \
|
||||
$(I)range.hpp $(I)segment.hpp $(I)ua.hpp $(I)xref.hpp \
|
||||
formchooser.cpp
|
||||
BIN
idasdk76/plugins/formchooser/smile.png
Normal file
BIN
idasdk76/plugins/formchooser/smile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 182 B |
Reference in New Issue
Block a user