mirror of
https://github.com/sanni/cartreader.git
synced 2024-12-25 04:21:53 +01:00
Add support for SUPER A'CAN
This commit is contained in:
parent
782db58ab1
commit
b57470510d
@ -146,6 +146,9 @@ char ver[5] = "12.3";
|
||||
// WonderSwan
|
||||
//#define enable_WS
|
||||
|
||||
// Super A'can
|
||||
#define enable_SUPRACAN
|
||||
|
||||
//******************************************
|
||||
// HW CONFIGS
|
||||
//******************************************
|
||||
@ -409,6 +412,8 @@ void print_STR(byte string_number, boolean newline) {
|
||||
#define mode_ARC 30
|
||||
#define mode_FAIRCHILD 31
|
||||
|
||||
#define mode_SUPRACAN 99
|
||||
|
||||
// optimization-safe nop delay
|
||||
#define NOP __asm__ __volatile__("nop\n\t")
|
||||
|
||||
@ -1002,8 +1007,9 @@ static const char modeItem16[] PROGMEM = "Magnavox Odyssey 2";
|
||||
static const char modeItem17[] PROGMEM = "Arcadia 2001";
|
||||
static const char modeItem18[] PROGMEM = "Fairchild Channel F";
|
||||
static const char modeItem19[] PROGMEM = "Flashrom Programmer";
|
||||
static const char modeItem99[] PROGMEM = "Super A'can";
|
||||
static const char modeItem20[] PROGMEM = "About";
|
||||
static const char* const modeOptions[] PROGMEM = { modeItem1, modeItem2, modeItem3, modeItem4, modeItem5, modeItem6, modeItem7, modeItem8, modeItem9, modeItem10, modeItem11, modeItem12, modeItem13, modeItem14, modeItem15, modeItem16, modeItem17, modeItem18, modeItem19, modeItem20 };
|
||||
static const char* const modeOptions[] PROGMEM = { modeItem1, modeItem2, modeItem3, modeItem4, modeItem5, modeItem6, modeItem7, modeItem8, modeItem9, modeItem10, modeItem11, modeItem12, modeItem13, modeItem14, modeItem15, modeItem16, modeItem17, modeItem18, modeItem99, modeItem19, modeItem20 };
|
||||
|
||||
// All included slots
|
||||
void mainMenu() {
|
||||
@ -1171,6 +1177,11 @@ void mainMenu() {
|
||||
case 17:
|
||||
setup_FAIRCHILD();
|
||||
fairchildMenu();
|
||||
|
||||
#ifdef enable_SUPRACAN
|
||||
case 99:
|
||||
setup_SuprAcan();
|
||||
mode = mode_SUPRACAN;
|
||||
break;
|
||||
#endif
|
||||
|
||||
@ -1236,6 +1247,10 @@ static const char consoles80Item3[] PROGMEM = "SMS/GG/MIII/SG-1000";
|
||||
//static const char consoles80Item4[] PROGMEM = "Reset"; (stored in common strings array)
|
||||
static const char* const consoles80Options[] PROGMEM = { consoles80Item1, consoles80Item2, consoles80Item3, string_reset2 };
|
||||
|
||||
// 90s Consoles submenu
|
||||
static const char consoles90Item1[] PROGMEM = "Super A'can";
|
||||
static const char* const consoles90Options[] PROGMEM = { consoles90Item1, string_reset2 };
|
||||
|
||||
// Handhelds submenu
|
||||
static const char handheldsItem1[] PROGMEM = "Virtual Boy";
|
||||
static const char handheldsItem2[] PROGMEM = "WonderSwan";
|
||||
@ -1347,7 +1362,6 @@ void consoles70Menu() {
|
||||
// Copy menuOptions out of progmem
|
||||
convertPgm(consoles70Options, 7);
|
||||
consoles70Menu = question_box(F("Choose Adapter"), menuOptions, 7, 0);
|
||||
|
||||
// wait for user choice to come back from the question box menu
|
||||
switch (consoles70Menu) {
|
||||
#ifdef enable_ATARI
|
||||
|
562
Cart_Reader/SUPRACAN.ino
Normal file
562
Cart_Reader/SUPRACAN.ino
Normal file
@ -0,0 +1,562 @@
|
||||
//******************************************
|
||||
// Super A'can MODULE
|
||||
//******************************************
|
||||
#ifdef enable_SUPRACAN
|
||||
|
||||
/******************************************
|
||||
Menu
|
||||
*****************************************/
|
||||
static const char acanMenuItem1[] PROGMEM = "Read Rom";
|
||||
static const char acanMenuItem2[] PROGMEM = "Read Save";
|
||||
static const char acanMenuItem3[] PROGMEM = "Write Save";
|
||||
static const char acanMenuItem4[] PROGMEM = "Read UM6650";
|
||||
static const char acanMenuItem5[] PROGMEM = "Write UM6650";
|
||||
|
||||
static const char* const menuOptionsAcan[] PROGMEM = {acanMenuItem1, acanMenuItem2, acanMenuItem3, acanMenuItem4, acanMenuItem5, string_reset2};
|
||||
|
||||
void setup_SuprAcan()
|
||||
{
|
||||
// addr as output
|
||||
DDRF = 0xff; // A0 - A7
|
||||
DDRK = 0xff; // A8 - A15
|
||||
DDRL = 0xff; // A16 - A23
|
||||
|
||||
// data as input
|
||||
DDRC = 0xff;
|
||||
DDRA = 0xff;
|
||||
PORTC = 0x00; // disable internal pull-up
|
||||
PORTA = 0x00;
|
||||
DDRC = 0x00; // D0 - D7
|
||||
DDRA = 0x00; // D8 - D15
|
||||
|
||||
// set /RST(PH0), /CS(PH3), C27(PH4), R/W(PH5), /AS(PH6) output
|
||||
DDRH |= ((1 << 0) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6));
|
||||
PORTH |= ((1 << 3) | (1 << 5) | (1 << 6));
|
||||
PORTH &= ~((1 << 0) | (1 << 4));
|
||||
|
||||
// set /CARTIN(PG5) input
|
||||
DDRG &= ~(1 << 5);
|
||||
// set 6619_124(PE4) input
|
||||
DDRE &= ~(1 << 4);
|
||||
|
||||
display_Clear();
|
||||
initializeClockOffset();
|
||||
|
||||
// clockgen
|
||||
if (i2c_found)
|
||||
{
|
||||
clockgen.set_freq(1073863500ULL, SI5351_CLK1); // cpu
|
||||
clockgen.set_freq(357954500ULL, SI5351_CLK2); // subcarrier
|
||||
clockgen.set_freq(5369317500ULL, SI5351_CLK0); // master clock
|
||||
|
||||
clockgen.output_enable(SI5351_CLK1, 1);
|
||||
clockgen.output_enable(SI5351_CLK2, 1);
|
||||
clockgen.output_enable(SI5351_CLK0, 0);
|
||||
|
||||
// Wait for clock generator
|
||||
clockgen.update_status();
|
||||
delay(500);
|
||||
}
|
||||
#ifdef clockgen_installed
|
||||
else
|
||||
{
|
||||
print_FatalError(F("Clock Generator not found"));
|
||||
}
|
||||
#endif
|
||||
|
||||
// /RST to 1
|
||||
PORTH |= (1 << 0);
|
||||
|
||||
cartSize = checkRomSize_Acan();
|
||||
romSize = cartSize >> 17;
|
||||
mode = mode_SUPRACAN;
|
||||
|
||||
if (cartSize == 0)
|
||||
print_Error(F("Unable to find rom signature..."));
|
||||
else
|
||||
{
|
||||
print_Msg(F("ROM Size: "));
|
||||
print_Msg(romSize);
|
||||
println_Msg(F(" Mb"));
|
||||
}
|
||||
|
||||
display_Update();
|
||||
wait();
|
||||
}
|
||||
|
||||
void suprAcanMenu()
|
||||
{
|
||||
uint8_t mainMenu;
|
||||
|
||||
convertPgm(menuOptionsAcan, 6);
|
||||
mainMenu = question_box(F("Super A'can Menu"), menuOptions, 6, 0);
|
||||
|
||||
switch (mainMenu)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
readROM_Acan();
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
readSRAM_Acan();
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
writeSRAM_Acan();
|
||||
verifySRAM_Acan();
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
readUM6650();
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
writeUM6650();
|
||||
verifyUM6650();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
resetCart_Acan();
|
||||
resetArduino();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
println_Msg(F(""));
|
||||
print_STR(press_button_STR, 1);
|
||||
display_Update();
|
||||
wait();
|
||||
}
|
||||
|
||||
static void readROM_Acan()
|
||||
{
|
||||
uint32_t crc32 = 0xffffffff;
|
||||
|
||||
EEPROM_readAnything(0, foldern);
|
||||
snprintf(folder, FILEPATH_LENGTH, "ACAN/ROM/%d", foldern);
|
||||
|
||||
display_Clear();
|
||||
print_STR(saving_to_STR, 0);
|
||||
print_Msg(folder);
|
||||
println_Msg(F("/..."));
|
||||
display_Update();
|
||||
|
||||
sd.mkdir(folder, true);
|
||||
sd.chdir(folder);
|
||||
|
||||
if (!myFile.open("rom.bin", O_RDWR | O_CREAT))
|
||||
print_FatalError(create_file_STR);
|
||||
|
||||
foldern++;
|
||||
EEPROM_writeAnything(0, foldern);
|
||||
|
||||
draw_progressbar(0, cartSize);
|
||||
|
||||
dataIn_MD();
|
||||
for (uint32_t addr = 0; addr < cartSize; addr += 512, draw_progressbar(addr, cartSize))
|
||||
{
|
||||
for (uint32_t i = 0; i < 512; i += 2)
|
||||
{
|
||||
*((uint16_t*)(sdBuffer + i)) = readWord_Acan(addr + i);
|
||||
UPDATE_CRC(crc32, sdBuffer[i]);
|
||||
UPDATE_CRC(crc32, sdBuffer[i + 1]);
|
||||
}
|
||||
|
||||
myFile.write(sdBuffer, 512);
|
||||
|
||||
if ((addr & ((1 << 14) - 1)) == 0)
|
||||
blinkLED();
|
||||
}
|
||||
|
||||
crc32 = ~crc32;
|
||||
myFile.close();
|
||||
|
||||
print_Msg(F("CRC32: "));
|
||||
print_Msg_PaddedHex32(crc32);
|
||||
println_Msg(F(""));
|
||||
print_STR(done_STR, 1);
|
||||
}
|
||||
|
||||
static void readSRAM_Acan()
|
||||
{
|
||||
// create a new folder for storing rom file
|
||||
EEPROM_readAnything(0, foldern);
|
||||
snprintf(folder, FILEPATH_LENGTH, "ACAN/SAVE/%d", foldern);
|
||||
|
||||
display_Clear();
|
||||
print_STR(saving_to_STR, 0);
|
||||
print_Msg(folder);
|
||||
println_Msg(F("/..."));
|
||||
display_Update();
|
||||
|
||||
sd.mkdir(folder, true);
|
||||
sd.chdir(folder);
|
||||
|
||||
if (!myFile.open("save.bin", O_RDWR | O_CREAT))
|
||||
print_FatalError(create_file_STR);
|
||||
|
||||
foldern++;
|
||||
EEPROM_writeAnything(0, foldern);
|
||||
|
||||
dataIn_MD();
|
||||
for (uint32_t i = 0; i < 0x10000; i += 1024)
|
||||
{
|
||||
for (uint32_t j = 0; j < 1024; j += 2)
|
||||
sdBuffer[(j >> 1)] = readWord_Acan(0xec0000 + i + j);
|
||||
|
||||
myFile.write(sdBuffer, 512);
|
||||
}
|
||||
|
||||
myFile.close();
|
||||
print_STR(done_STR, 1);
|
||||
}
|
||||
|
||||
static void writeSRAM_Acan()
|
||||
{
|
||||
filePath[0] = 0;
|
||||
sd.chdir("/");
|
||||
fileBrowser(F("Select a file"));
|
||||
snprintf(filePath, FILEPATH_LENGTH, "%s/%s", filePath, fileName);
|
||||
|
||||
display_Clear();
|
||||
|
||||
if (!myFile.open(filePath, O_READ))
|
||||
{
|
||||
print_Error(F("File doesn't exist"));
|
||||
return;
|
||||
}
|
||||
|
||||
print_Msg(F("Writing "));
|
||||
print_Msg(filePath);
|
||||
println_Msg(F("..."));
|
||||
display_Update();
|
||||
|
||||
dataOut_MD();
|
||||
for (uint32_t i = 0; i < 0x10000 && myFile.available(); i += 1024)
|
||||
{
|
||||
myFile.read(sdBuffer, 512);
|
||||
|
||||
for (uint32_t j = 0; j < 1024; j += 2)
|
||||
writeWord_Acan(0xec0000 + i + j, sdBuffer[(j >> 1)]);
|
||||
}
|
||||
|
||||
myFile.close();
|
||||
|
||||
dataIn_MD();
|
||||
print_STR(done_STR, 1);
|
||||
}
|
||||
|
||||
static void verifySRAM_Acan()
|
||||
{
|
||||
print_STR(verifying_STR, 0);
|
||||
display_Update();
|
||||
|
||||
if (!myFile.open(filePath, O_READ))
|
||||
{
|
||||
print_Error(F("File doesn't exist"));
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t write_errors = 0;
|
||||
|
||||
dataIn_MD();
|
||||
for (uint32_t i = 0; i < 0x10000 && myFile.available(); i += 1024)
|
||||
{
|
||||
myFile.read(sdBuffer, 512);
|
||||
|
||||
for (uint32_t j = 0; j < 1024; j += 2)
|
||||
{
|
||||
if (readWord_Acan(0xec0000 + i + j) != sdBuffer[(j >> 1)])
|
||||
write_errors++;
|
||||
}
|
||||
}
|
||||
|
||||
myFile.close();
|
||||
|
||||
if (write_errors == 0)
|
||||
{
|
||||
println_Msg(F("passed"));
|
||||
}
|
||||
else
|
||||
{
|
||||
println_Msg(F("failed"));
|
||||
print_Msg(F("Error: "));
|
||||
print_Msg(write_errors);
|
||||
println_Msg(F(" bytes "));
|
||||
print_Error(did_not_verify_STR);
|
||||
}
|
||||
}
|
||||
|
||||
static void readUM6650()
|
||||
{
|
||||
// create a new folder for storing rom file
|
||||
EEPROM_readAnything(0, foldern);
|
||||
snprintf(folder, sizeof(folder), "ACAN/UM6650/%d", foldern);
|
||||
|
||||
display_Clear();
|
||||
print_STR(saving_to_STR, 0);
|
||||
print_Msg(folder);
|
||||
println_Msg(F("/..."));
|
||||
display_Update();
|
||||
|
||||
sd.mkdir(folder, true);
|
||||
sd.chdir(folder);
|
||||
|
||||
if (!myFile.open("UM6650.bin", O_RDWR | O_CREAT))
|
||||
print_FatalError(create_file_STR);
|
||||
|
||||
foldern++;
|
||||
EEPROM_writeAnything(0, foldern);
|
||||
|
||||
for (uint16_t i = 0; i < 256; i++)
|
||||
{
|
||||
dataOut_MD();
|
||||
writeWord_Acan(0xeb0d03, i);
|
||||
|
||||
dataIn_MD();
|
||||
sdBuffer[i] = readWord_Acan(0xeb0d01);
|
||||
}
|
||||
|
||||
myFile.write(sdBuffer, 256);
|
||||
myFile.close();
|
||||
|
||||
print_STR(done_STR, 1);
|
||||
}
|
||||
|
||||
static void verifyUM6650()
|
||||
{
|
||||
print_STR(verifying_STR, 0);
|
||||
display_Update();
|
||||
|
||||
if (!myFile.open(filePath, O_READ))
|
||||
{
|
||||
print_Error(F("File doesn't exist"));
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t write_errors = 0;
|
||||
uint16_t len = myFile.read(sdBuffer, 256);
|
||||
myFile.close();
|
||||
|
||||
for (uint16_t i = 0; i < len; i++)
|
||||
{
|
||||
dataOut_MD();
|
||||
writeWord_Acan(0xeb0d03, i);
|
||||
|
||||
dataIn_MD();
|
||||
if (readWord_Acan(0xeb0d01) != sdBuffer[i])
|
||||
write_errors++;
|
||||
}
|
||||
|
||||
if (write_errors)
|
||||
{
|
||||
println_Msg(F("failed"));
|
||||
print_Msg(F("Error: "));
|
||||
print_Msg(write_errors);
|
||||
println_Msg(F(" bytes "));
|
||||
print_Error(did_not_verify_STR);
|
||||
}
|
||||
else
|
||||
{
|
||||
println_Msg(F("passed"));
|
||||
}
|
||||
}
|
||||
|
||||
static void writeUM6650()
|
||||
{
|
||||
filePath[0] = 0;
|
||||
sd.chdir("/");
|
||||
fileBrowser(F("Select a file"));
|
||||
snprintf(filePath, FILEPATH_LENGTH, "%s/%s", filePath, fileName);
|
||||
|
||||
display_Clear();
|
||||
|
||||
if (!myFile.open(filePath, O_READ))
|
||||
{
|
||||
print_Error(F("File doesn't exist"));
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t len = myFile.read(sdBuffer, 256);
|
||||
myFile.close();
|
||||
|
||||
print_Msg(F("Writing "));
|
||||
print_Msg(filePath);
|
||||
println_Msg(F("..."));
|
||||
display_Update();
|
||||
|
||||
dataOut_MD();
|
||||
for (uint16_t i = 0; i < len; i++)
|
||||
{
|
||||
writeWord_Acan(0xeb0d03, i);
|
||||
writeWord_Acan(0xeb0d01, sdBuffer[i]);
|
||||
|
||||
delay(10); // for AT28C64B write
|
||||
}
|
||||
|
||||
dataIn_MD();
|
||||
print_STR(done_STR, 1);
|
||||
}
|
||||
|
||||
static uint32_t checkRomSize_Acan()
|
||||
{
|
||||
uint32_t addr = 0;
|
||||
uint32_t crc32;
|
||||
|
||||
do
|
||||
{
|
||||
// check if there is rom chip exists
|
||||
// pull-up enable
|
||||
DDRC = 0xff;
|
||||
DDRA = 0xff;
|
||||
PORTC = 0xff;
|
||||
PORTA = 0xff;
|
||||
DDRC = 0x00;
|
||||
DDRA = 0x00;
|
||||
*((uint16_t*)sdBuffer) = readWord_Acan(addr);
|
||||
|
||||
// pull-up disable
|
||||
DDRC = 0xff;
|
||||
DDRA = 0xff;
|
||||
PORTC = 0x00;
|
||||
PORTA = 0x00;
|
||||
DDRC = 0x00;
|
||||
DDRA = 0x00;
|
||||
*((uint16_t*)(sdBuffer + 2)) = readWord_Acan(addr);
|
||||
|
||||
// should be them same if chip exists
|
||||
if (sdBuffer[0] != sdBuffer[2] || sdBuffer[1] != sdBuffer[3])
|
||||
break;
|
||||
|
||||
crc32 = 0xffffffff;
|
||||
|
||||
for (uint32_t i = 0x2000; i < 0x2080; i += 2)
|
||||
{
|
||||
*((uint16_t*)sdBuffer) = readWord_Acan(addr + i);
|
||||
UPDATE_CRC(crc32, sdBuffer[0]);
|
||||
UPDATE_CRC(crc32, sdBuffer[1]);
|
||||
}
|
||||
|
||||
crc32 = ~crc32;
|
||||
|
||||
if (crc32 == 0xa2bc9d7a)
|
||||
{
|
||||
if (addr > 0)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (addr == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
addr += 0x80000;
|
||||
|
||||
} while (addr < 0x800000);
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
static void resetCart_Acan()
|
||||
{
|
||||
// set /CS(PH3), R/W(PH5), /AS(PH6) high
|
||||
// /RST(PH0) and C27(PH4) low
|
||||
PORTH |= ((1 << 3) | (1 << 5) | (1 << 6));
|
||||
PORTH &= ~((1 << 0) | (1 << 4));
|
||||
|
||||
if (i2c_found)
|
||||
{
|
||||
clockgen.output_enable(SI5351_CLK1, 0); // CPU clock
|
||||
clockgen.output_enable(SI5351_CLK2, 0); // CIC clock
|
||||
clockgen.output_enable(SI5351_CLK0, 0); // master clock
|
||||
}
|
||||
}
|
||||
|
||||
static void writeWord_Acan(uint32_t addr, uint16_t data)
|
||||
{
|
||||
uint8_t *ptr = (uint8_t*)&addr;
|
||||
|
||||
PORTF = *ptr++;
|
||||
PORTK = *ptr++;
|
||||
PORTL = *ptr;
|
||||
|
||||
if (*ptr < 0xe0)
|
||||
{
|
||||
// ROM area
|
||||
// /CS(PH3), C27(PH4), R/W(PH5), /AS(PH6) to L
|
||||
PORTH &= ~((1 << 3) | (1 << 4) | (1 << 5) | (1 << 6));
|
||||
}
|
||||
else if (*ptr == 0xec)
|
||||
{
|
||||
// save area
|
||||
// /CS(PH3) to H, C27(PH4), R/W(PH5), /AS(PH6) to L
|
||||
PORTH |= (1 << 3);
|
||||
PORTH &= ~((1 << 4) | (1 << 5) | (1 << 6));
|
||||
}
|
||||
else if (addr == 0x00eb0d03 || addr == 0x00eb0d01)
|
||||
{
|
||||
// UM6650 area
|
||||
// /CS(PH3), C27(PH4) to H, R/W(PH5), /AS(PH6) to L
|
||||
PORTH |= ((1 << 3) | (1 << 4));
|
||||
PORTH &= ~((1 << 5) | (1 << 6));
|
||||
}
|
||||
|
||||
ptr = (uint8_t*)&data;
|
||||
PORTC = *ptr++;
|
||||
PORTA = *ptr;
|
||||
|
||||
NOP; NOP; NOP;
|
||||
|
||||
PORTH &= ~(1 << 4);
|
||||
PORTH |= ((1 << 3) | (1 << 5) | (1 << 6));
|
||||
}
|
||||
|
||||
static uint16_t readWord_Acan(uint32_t addr)
|
||||
{
|
||||
uint8_t *ptr = (uint8_t*)&addr;
|
||||
uint16_t data;
|
||||
|
||||
PORTF = *ptr++;
|
||||
PORTK = *ptr++;
|
||||
PORTL = *ptr;
|
||||
|
||||
if (*ptr < 0xe0)
|
||||
{
|
||||
// ROM area
|
||||
// /CS(PH3), C27(PH4), /AS(PH6) to L
|
||||
PORTH &= ~((1 << 3) | (1 << 4) | (1 << 6));
|
||||
}
|
||||
else if (*ptr == 0xec)
|
||||
{
|
||||
// save area
|
||||
// /CS(PH3) to H, C27(PH4), /AS(PH6) to L
|
||||
PORTH |= (1 << 3);
|
||||
PORTH &= ~((1 << 4) | (1 << 6));
|
||||
}
|
||||
else if (addr == 0x00eb0d03 || addr == 0x00eb0d01)
|
||||
{
|
||||
// UM6650 area
|
||||
// /CS(PH3), C27(PH4) to H, /AS(PH6) to L
|
||||
PORTH |= ((1 << 3) | (1 << 4));
|
||||
PORTH &= ~(1 << 6);
|
||||
}
|
||||
|
||||
ptr = (uint8_t*)&data;
|
||||
NOP; NOP; NOP;
|
||||
|
||||
*ptr++ = PINC;
|
||||
*ptr = PINA;
|
||||
|
||||
PORTH &= ~(1 << 4);
|
||||
PORTH |= ((1 << 3) | (1 << 5) | (1 << 6));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user