mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 05:32:47 +00:00
Enabled a write permission system
Plus tons of design and under the hood changes
This commit is contained in:
parent
44c351c1a7
commit
bf19767a82
@ -171,30 +171,92 @@ bool ShowPrompt(bool ask, const char *format, ...)
|
||||
u32 str_width, str_height;
|
||||
u32 x, y;
|
||||
|
||||
char str[384] = {}; // 384 should be more than enough
|
||||
char str[512] = {}; // 512 should be more than enough
|
||||
va_list va;
|
||||
|
||||
va_start(va, format);
|
||||
vsnprintf(str, 384, format, va);
|
||||
vsnprintf(str, 512, format, va);
|
||||
va_end(va);
|
||||
|
||||
str_width = GetDrawStringWidth(str);
|
||||
str_height = GetDrawStringHeight(str) + (2 * 10);
|
||||
x = (str_width >= SCREEN_WIDTH_TOP) ? 0 : (SCREEN_WIDTH_TOP - str_width) / 2;
|
||||
y = (str_height >= SCREEN_HEIGHT) ? 0 : (SCREEN_HEIGHT - str_height) / 2;
|
||||
y = (str_height >= SCREEN_HEIGHT) ? 0 : (SCREEN_HEIGHT - (str_height+20)) / 2;
|
||||
|
||||
ClearScreenF(true, false, COLOR_STD_BG);
|
||||
DrawStringF(true, x, y, COLOR_STD_FONT, COLOR_STD_BG, (ask) ? "%s\n\n(<A> to continue, <B> to cancel)" : "%s\n\n(<A> to continue)", str);
|
||||
DrawStringF(true, x, y, COLOR_STD_FONT, COLOR_STD_BG, str);
|
||||
DrawStringF(true, x, y + str_height, COLOR_STD_FONT, COLOR_STD_BG, (ask) ? "(<A> yes, <B> no)" : "(<A> to continue)");
|
||||
|
||||
while (true) {
|
||||
u32 pad_state = InputWait();
|
||||
if (pad_state & BUTTON_A) break;
|
||||
else if (pad_state & BUTTON_B) return false;
|
||||
else if (ask && (pad_state & BUTTON_B)) return false;
|
||||
}
|
||||
|
||||
ClearScreenF(true, false, COLOR_STD_BG);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShowUnlockSequence(u32 seqlvl, const char *format, ...) {
|
||||
const u32 seqcolors[4] = { COLOR_STD_FONT, COLOR_GREEN, COLOR_YELLOW, COLOR_RED };
|
||||
const u32 sequences[4][5] = {
|
||||
{ BUTTON_RIGHT, BUTTON_DOWN, BUTTON_RIGHT, BUTTON_DOWN, BUTTON_A },
|
||||
{ BUTTON_LEFT, BUTTON_DOWN, BUTTON_RIGHT, BUTTON_UP, BUTTON_A },
|
||||
{ BUTTON_LEFT, BUTTON_RIGHT, BUTTON_DOWN, BUTTON_UP, BUTTON_A },
|
||||
{ BUTTON_LEFT, BUTTON_UP, BUTTON_RIGHT, BUTTON_UP, BUTTON_A }
|
||||
};
|
||||
const char seqsymbols[4][5] = {
|
||||
{ '\x1A', '\x19', '\x1A', '\x19', 'A' },
|
||||
{ '\x1B', '\x19', '\x1A', '\x18', 'A' },
|
||||
{ '\x1B', '\x1A', '\x19', '\x18', 'A' },
|
||||
{ '\x1B', '\x18', '\x1A', '\x18', 'A' },
|
||||
};
|
||||
const u32 len = 5;
|
||||
u32 lvl = 0;
|
||||
|
||||
u32 str_width, str_height;
|
||||
u32 x, y;
|
||||
|
||||
char str[512] = {}; // 512 should be more than enough
|
||||
va_list va;
|
||||
|
||||
va_start(va, format);
|
||||
vsnprintf(str, 512, format, va);
|
||||
va_end(va);
|
||||
|
||||
str_width = GetDrawStringWidth(str);
|
||||
str_height = GetDrawStringHeight(str) + (2 * 10);
|
||||
x = (str_width >= SCREEN_WIDTH_TOP) ? 0 : (SCREEN_WIDTH_TOP - str_width) / 2;
|
||||
y = (str_height >= SCREEN_HEIGHT) ? 0 : (SCREEN_HEIGHT - (str_height + 30)) / 2;
|
||||
|
||||
ClearScreenF(true, false, COLOR_STD_BG);
|
||||
DrawStringF(true, x, y, COLOR_STD_FONT, COLOR_STD_BG, str);
|
||||
DrawStringF(true, x, y + str_height, COLOR_STD_FONT, COLOR_STD_BG, "To proceed, enter this:");
|
||||
|
||||
while (true) {
|
||||
for (u32 n = 0; n < len; n++) {
|
||||
DrawStringF(true, x + (n*4*8), y + str_height + 10,
|
||||
(lvl > n) ? seqcolors[seqlvl] : COLOR_GREY, COLOR_STD_BG, "<%c>", seqsymbols[seqlvl][n]);
|
||||
}
|
||||
if (lvl == len)
|
||||
break;
|
||||
u32 pad_state = InputWait();
|
||||
if (!(pad_state & BUTTON_ANY))
|
||||
continue;
|
||||
else if (pad_state & sequences[seqlvl][lvl])
|
||||
lvl++;
|
||||
else if (pad_state & BUTTON_B)
|
||||
break;
|
||||
else if (lvl == 0 || !(pad_state & sequences[seqlvl][lvl-1]))
|
||||
lvl = 0;
|
||||
}
|
||||
|
||||
ClearScreenF(true, false, COLOR_STD_BG);
|
||||
|
||||
return (lvl >= len);
|
||||
}
|
||||
|
||||
void ShowProgress(u64 current, u64 total, const char* opstr, bool clearscreen)
|
||||
{
|
||||
const u32 bar_width = 240;
|
||||
@ -202,14 +264,15 @@ void ShowProgress(u64 current, u64 total, const char* opstr, bool clearscreen)
|
||||
const u32 bar_pos_x = (SCREEN_WIDTH_TOP - bar_width) / 2;
|
||||
const u32 bar_pos_y = (SCREEN_HEIGHT / 2) - bar_height - 2;
|
||||
const u32 text_pos_y = (SCREEN_HEIGHT / 2);
|
||||
u32 prog_width = ((total > 0) && (current <= total)) ? (current * (bar_width-2)) / total : 0;
|
||||
u32 prog_width = ((total > 0) && (current <= total)) ? (current * (bar_width-4)) / total : 0;
|
||||
char tempstr[64];
|
||||
|
||||
if (clearscreen) ClearScreenF(true, false, COLOR_STD_BG);
|
||||
DrawRectangleF(true, bar_pos_x, bar_pos_y, bar_width, bar_height, COLOR_DARKGREY);
|
||||
DrawRectangleF(true, bar_pos_x + 1, bar_pos_y + 1, bar_width - 2, bar_height - 2, COLOR_STD_BG);
|
||||
DrawRectangleF(true, bar_pos_x + 1, bar_pos_y + 1, prog_width, bar_height - 2, COLOR_STD_FONT);
|
||||
DrawRectangleF(true, bar_pos_x + 2, bar_pos_y + 2, prog_width, bar_height - 4, COLOR_STD_FONT);
|
||||
|
||||
ResizeString(tempstr, opstr, 28, 8, false);
|
||||
DrawStringF(true, bar_pos_x, text_pos_y, COLOR_STD_FONT, COLOR_STD_BG, tempstr);
|
||||
DrawString(TOP_SCREEN0, tempstr, bar_pos_x, text_pos_y, COLOR_STD_FONT, COLOR_STD_BG);
|
||||
DrawString(TOP_SCREEN1, tempstr, bar_pos_x, text_pos_y, COLOR_STD_FONT, COLOR_STD_BG);
|
||||
}
|
||||
|
@ -23,6 +23,10 @@
|
||||
#define COLOR_YELLOW RGB(0xFF, 0xFF, 0x00)
|
||||
#define COLOR_CYAN RGB(0xFF, 0x00, 0xFF)
|
||||
|
||||
#define COLOR_BRIGHTRED RGB(0xFF, 0x30, 0x30)
|
||||
#define COLOR_BRIGHTYELLOW RGB(0xFF, 0xFF, 0x30)
|
||||
#define COLOR_BRIGHTGREEN RGB(0x30, 0xFF, 0x30)
|
||||
|
||||
#define COLOR_TINTEDBLUE RGB(0x60, 0x60, 0x80)
|
||||
#define COLOR_TINTEDYELLOW RGB(0xD0, 0xD0, 0x60)
|
||||
#define COLOR_TINTEDGREEN RGB(0x70, 0x80, 0x70)
|
||||
@ -65,4 +69,5 @@ void TruncateString(char* dest, const char* orig, int nsize, int tpos);
|
||||
void FormatBytes(char* str, u64 bytes);
|
||||
|
||||
bool ShowPrompt(bool ask, const char *format, ...);
|
||||
bool ShowUnlockSequence(u32 seqlvl, const char *format, ...);
|
||||
void ShowProgress(u64 current, u64 total, const char* opstr, bool clearscreen);
|
||||
|
36
source/fs.c
36
source/fs.c
@ -54,13 +54,16 @@ bool CheckWritePermissions(const char* path) {
|
||||
}
|
||||
|
||||
if ((pdrv >= 1) && (pdrv <= 3) && (write_permission_level < 3)) {
|
||||
ShowPrompt(false, "Writing to the SysNAND is locked!\nUnlock it from the root menu.");
|
||||
if (ShowPrompt(true, "Writing to the SysNAND is locked!\nUnlock it now?"))
|
||||
return SetWritePermissions(3);
|
||||
return false;
|
||||
} else if ((pdrv >= 4) && (pdrv <= 6) && (write_permission_level < 2)) {
|
||||
ShowPrompt(false, "Writing to the EmuNAND is locked!\nUnlock it from the root menu.");
|
||||
if (ShowPrompt(true, "Writing to the EmuNAND is locked!\nUnlock it now?"))
|
||||
return SetWritePermissions(2);
|
||||
return false;
|
||||
} else if ((pdrv == 0) && (write_permission_level < 1)) {
|
||||
ShowPrompt(false, "Writing to the SD card is locked!\nUnlock it from the root menu.");
|
||||
if (ShowPrompt(true, "Writing to the SD card is locked!\nUnlock it now?"))
|
||||
return SetWritePermissions(1);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -68,11 +71,38 @@ bool CheckWritePermissions(const char* path) {
|
||||
}
|
||||
|
||||
bool SetWritePermissions(u32 level) {
|
||||
if (write_permission_level >= level) {
|
||||
// no need to ask the user here
|
||||
write_permission_level = level;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (level) {
|
||||
case 1:
|
||||
if (!ShowUnlockSequence(1, "You want to enable SD card\nwriting permissions."))
|
||||
return false;
|
||||
break;
|
||||
case 2:
|
||||
if (!ShowUnlockSequence(2, "You want to enable EmuNAND\nwriting permissions.\nThis is potentially dangerous!\nKeep a backup, just in case."))
|
||||
return false;
|
||||
break;
|
||||
case 3:
|
||||
if (!ShowUnlockSequence(3, "!This is your only warning!\n \nYou want to enable SysNAND\nwriting permissions.\nThis is potentially dangerous\nand can brick your 3DS!\nHaving a SysNAND backup and\nNANDmod is recommended."))
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
write_permission_level = level;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 GetWritePermissions() {
|
||||
return write_permission_level;
|
||||
}
|
||||
|
||||
bool FileCreate(const char* path, u8* data, u32 size) {
|
||||
FIL file;
|
||||
UINT bytes_written = 0;
|
||||
|
@ -32,6 +32,9 @@ bool CheckWritePermissions(const char* path);
|
||||
/** Set new write permissions */
|
||||
bool SetWritePermissions(u32 level);
|
||||
|
||||
/** Get write permissions */
|
||||
u32 GetWritePermissions();
|
||||
|
||||
/** Create / overwrite file and write the provided data to it **/
|
||||
bool FileCreate(const char* path, u8* data, u32 size);
|
||||
|
||||
|
119
source/godmode.c
119
source/godmode.c
@ -3,19 +3,32 @@
|
||||
#include "hid.h"
|
||||
#include "fs.h"
|
||||
|
||||
#define COLOR_TOP_BAR COLOR_WHITE
|
||||
#define COLOR_TOP_BAR ((GetWritePermissions() == 0) ? COLOR_WHITE : (GetWritePermissions() == 1) ? COLOR_BRIGHTGREEN : (GetWritePermissions() == 2) ? COLOR_BRIGHTYELLOW : COLOR_BRIGHTRED)
|
||||
#define COLOR_SIDE_BAR COLOR_DARKGREY
|
||||
#define COLOR_MARKED COLOR_TINTEDYELLOW
|
||||
#define COLOR_FILE COLOR_TINTEDGREEN
|
||||
#define COLOR_DIR COLOR_TINTEDBLUE
|
||||
#define COLOR_ROOT COLOR_GREY
|
||||
|
||||
void DrawUserInterface(const char* curr_path, DirEntry* curr_entry, DirStruct* clipboard) {
|
||||
const u32 info_start = 16;
|
||||
void DrawUserInterface(const char* curr_path, DirEntry* curr_entry, DirStruct* clipboard, bool switched) {
|
||||
const u32 info_start = 18;
|
||||
|
||||
static u32 state_prev = 0xFFFFFFFF;
|
||||
u32 state_curr =
|
||||
((*curr_path) ? (1<<0) : 0) |
|
||||
((clipboard->n_entries) ? (1<<1) : 0) |
|
||||
((switched) ? (1<<2) : 0) |
|
||||
(GetWritePermissions()<<3);
|
||||
|
||||
char bytestr0[32];
|
||||
char bytestr1[32];
|
||||
char tempstr[64];
|
||||
|
||||
if (state_prev != state_curr) {
|
||||
ClearScreenF(true, false, COLOR_STD_BG);
|
||||
state_prev = state_curr;
|
||||
}
|
||||
|
||||
// top bar - current path & free/total storage
|
||||
DrawRectangleF(true, 0, 0, SCREEN_WIDTH_TOP, 12, COLOR_TOP_BAR);
|
||||
if (strncmp(curr_path, "", 256) != 0) {
|
||||
@ -52,11 +65,25 @@ void DrawUserInterface(const char* curr_path, DirEntry* curr_entry, DirStruct* c
|
||||
}
|
||||
*tempstr = '\0';
|
||||
if (clipboard->n_entries > 10) snprintf(tempstr, 60, "+ %lu more", clipboard->n_entries - 10);
|
||||
DrawStringF(true, SCREEN_WIDTH_TOP - (20*8) - 4, info_start + 12 + (10*10), COLOR_GREY, COLOR_STD_BG, "%20s", tempstr);
|
||||
DrawStringF(true, SCREEN_WIDTH_TOP - (20*8) - 4, info_start + 12 + (10*10), COLOR_DARKGREY, COLOR_STD_BG, "%20s", tempstr);
|
||||
|
||||
|
||||
// bottom: inctruction block
|
||||
char* instr = "GodMode 9 v0.0.3\n<A>/<B>/<\x18\x19\x1A\x1B> - Navigation\n<L>(+<\x18\x19\x1A\x1B> - Mark entries(s)\n<X> - Make a Screenshot\n<START/+\x1B> - Reboot/Poweroff";
|
||||
char instr[256];
|
||||
snprintf(instr, 256, "%s%s%s%s%s%s",
|
||||
"GodMode 9 v0.0.4\n", // generic start part
|
||||
(*curr_path && !switched) ? "<R> (hold) - Switch commands\n<L> (+<\x18\x19\x1A\x1B>) - Mark entries\n" :
|
||||
(*curr_path && switched) ? "<R> (rel.) - Switch commands\n<L> - Make a Screenshot\n" :
|
||||
"<R+L> - Make a Screenshot\n",
|
||||
(!(*curr_path)) ? "" :
|
||||
(!switched) ? "<X> - DELETE file(s)\n<Y> - ADD file(s) to clipboard\n" :
|
||||
"<X> - RENAME file\n<Y> - CREATE directory\n",
|
||||
(*curr_path) ? "" :
|
||||
(GetWritePermissions() <= 1) ? "<X> - Unlock EmuNAND writing\n<Y> - Unlock SysNAND writing\n" :
|
||||
(GetWritePermissions() == 2) ? "<X> - Relock EmuNAND writing\n<Y> - Unlock SysNAND writing\n" :
|
||||
"<X> - Relock EmuNAND writing\n<Y> - Relock SysNAND writing\n",
|
||||
(clipboard->n_entries) ? "<SELECT> - Clear Clipboard\n" : "", // only if clipboard is full
|
||||
"<START/+\x1B> - Reboot/Poweroff"); // generic end part
|
||||
DrawStringF(true, (SCREEN_WIDTH_TOP - GetDrawStringWidth(instr)) / 2, SCREEN_HEIGHT - 2 - GetDrawStringHeight(instr), COLOR_STD_FONT, COLOR_STD_BG, instr);
|
||||
}
|
||||
|
||||
@ -115,54 +142,72 @@ u32 GodMode() {
|
||||
char current_path[256] = { 0x00 };
|
||||
|
||||
int mark_setting = -1;
|
||||
bool switched = false;
|
||||
u32 cursor = 0;
|
||||
|
||||
ClearScreenF(true, true, COLOR_BLACK);
|
||||
ClearScreenF(true, true, COLOR_STD_BG);
|
||||
if (!InitFS()) return exit_mode;
|
||||
|
||||
GetDirContents(current_dir, "");
|
||||
clipboard->n_entries = 0;
|
||||
while (true) { // this is the main loop
|
||||
DrawUserInterface(current_path, &(current_dir->entry[cursor]), clipboard); // no need to fully do this everytime!
|
||||
DrawUserInterface(current_path, &(current_dir->entry[cursor]), clipboard, switched);
|
||||
DrawDirContents(current_dir, cursor);
|
||||
u32 pad_state = InputWait();
|
||||
if (pad_state & BUTTON_DOWN) { // cursor up
|
||||
cursor++;
|
||||
if (cursor >= current_dir->n_entries)
|
||||
cursor = current_dir->n_entries - 1;
|
||||
} else if ((pad_state & BUTTON_UP) && cursor) { // cursor down
|
||||
cursor--;
|
||||
} else if ((pad_state & BUTTON_RIGHT) && (mark_setting < 0)) { // cursor down (quick)
|
||||
cursor += quick_stp;
|
||||
if (cursor >= current_dir->n_entries)
|
||||
cursor = current_dir->n_entries - 1;
|
||||
} else if ((pad_state & BUTTON_LEFT) && (mark_setting < 0)) { // curor up (quick)
|
||||
cursor = (cursor >= quick_stp) ? cursor - quick_stp : 0;
|
||||
} else if (pad_state & BUTTON_RIGHT) { // mark all entries
|
||||
for (u32 c = 0; c < current_dir->n_entries; c++) current_dir->entry[c].marked = 1;
|
||||
} else if (pad_state & BUTTON_LEFT) { // unmark all entries
|
||||
for (u32 c = 0; c < current_dir->n_entries; c++) current_dir->entry[c].marked = 0;
|
||||
} else if ((pad_state & BUTTON_L1) && *current_path) { // switch marking for single entry
|
||||
if (mark_setting >= 0) {
|
||||
current_dir->entry[cursor].marked = mark_setting;
|
||||
} else {
|
||||
current_dir->entry[cursor].marked ^= 0x1;
|
||||
mark_setting = current_dir->entry[cursor].marked;
|
||||
switched = (pad_state & BUTTON_R1);
|
||||
if (!(*current_path) || switched || !(pad_state & BUTTON_L1)) {
|
||||
mark_setting = -1;
|
||||
}
|
||||
} else if ((pad_state & BUTTON_A) && (current_dir->entry[cursor].type != T_FAT_FILE)) { // one level up
|
||||
|
||||
// commands which are valid anywhere
|
||||
if ((pad_state & BUTTON_A) && (current_dir->entry[cursor].type != T_FAT_FILE)) { // one level up
|
||||
strncpy(current_path, current_dir->entry[cursor].path, 256);
|
||||
GetDirContents(current_dir, current_path);
|
||||
cursor = 0;
|
||||
ClearScreenF(true, true, COLOR_STD_BG); // not really required
|
||||
} else if (pad_state & BUTTON_B) { // one level down
|
||||
char* last_slash = strrchr(current_path, '/');
|
||||
if (last_slash) *last_slash = '\0';
|
||||
else *current_path = '\0';
|
||||
GetDirContents(current_dir, current_path);
|
||||
cursor = 0;
|
||||
ClearScreenF(true, true, COLOR_STD_BG); // not really required
|
||||
} else if (pad_state & BUTTON_X) { // create a screenshot
|
||||
} else if ((pad_state & BUTTON_DOWN) && (cursor < current_dir->n_entries - 1)) { // cursor up
|
||||
cursor++;
|
||||
} else if ((pad_state & BUTTON_UP) && cursor) { // cursor down
|
||||
cursor--;
|
||||
} else if ((pad_state & BUTTON_RIGHT) && (mark_setting < 0)) { // cursor down (quick)
|
||||
cursor += quick_stp;
|
||||
if (cursor >= current_dir->n_entries)
|
||||
cursor = current_dir->n_entries - 1;
|
||||
} else if ((pad_state & BUTTON_LEFT) && (mark_setting < 0)) { // cursor up (quick)
|
||||
cursor = (cursor >= quick_stp) ? cursor - quick_stp : 0;
|
||||
} else if (pad_state & BUTTON_RIGHT) { // mark all entries
|
||||
for (u32 c = 0; c < current_dir->n_entries; c++) current_dir->entry[c].marked = 1;
|
||||
} else if (pad_state & BUTTON_LEFT) { // unmark all entries
|
||||
for (u32 c = 0; c < current_dir->n_entries; c++) current_dir->entry[c].marked = 0;
|
||||
} else if (switched && (pad_state & BUTTON_L1)) { // switched L -> screenshot
|
||||
CreateScreenshot();
|
||||
ClearScreenF(true, true, COLOR_STD_BG);
|
||||
} else if (*current_path && (pad_state & BUTTON_L1)) { // unswitched L - mark/unmark single entry
|
||||
if (mark_setting >= 0) {
|
||||
current_dir->entry[cursor].marked = mark_setting;
|
||||
} else {
|
||||
current_dir->entry[cursor].marked ^= 0x1;
|
||||
mark_setting = current_dir->entry[cursor].marked;
|
||||
}
|
||||
} else if ((pad_state & BUTTON_SELECT) && (clipboard->n_entries > 0)) { // clear clipboard
|
||||
clipboard->n_entries = 0;
|
||||
}
|
||||
|
||||
// highly specific commands
|
||||
if (!(*current_path)) { // in the root folder...
|
||||
if (pad_state & BUTTON_X) {
|
||||
SetWritePermissions((GetWritePermissions() >= 2) ? 1 : 2);
|
||||
} else if (pad_state & BUTTON_Y) {
|
||||
SetWritePermissions((GetWritePermissions() >= 3) ? 2 : 3);
|
||||
}
|
||||
} else if (!switched) { // standard unswitched command set
|
||||
if (pad_state & BUTTON_X) { // delete a file
|
||||
// not implemented yet
|
||||
} else if ((pad_state & BUTTON_Y) && (clipboard->n_entries == 0)) { // fill clipboard
|
||||
for (u32 c = 0; c < current_dir->n_entries; c++) {
|
||||
if (current_dir->entry[c].marked) {
|
||||
@ -175,13 +220,11 @@ u32 GodMode() {
|
||||
DirEntryCpy(&(clipboard->entry[0]), &(current_dir->entry[cursor]));
|
||||
clipboard->n_entries = 1;
|
||||
}
|
||||
} else if ((pad_state & BUTTON_SELECT) && (clipboard->n_entries > 0)) { // clear clipboard
|
||||
clipboard->n_entries = 0;
|
||||
}
|
||||
} else { // switched command set
|
||||
// not implemented yet
|
||||
}
|
||||
|
||||
if (!(pad_state & BUTTON_L1)) {
|
||||
mark_setting = -1;
|
||||
}
|
||||
if (pad_state & BUTTON_START) {
|
||||
exit_mode = (pad_state & BUTTON_LEFT) ? GODMODE_EXIT_POWEROFF : GODMODE_EXIT_REBOOT;
|
||||
break;
|
||||
|
Loading…
x
Reference in New Issue
Block a user