2015-11-13 13:23:43 +00:00
|
|
|
#include "stdafx.h"
|
2016-01-13 07:23:22 +00:00
|
|
|
#include <Project64-core/Logging.h>
|
2015-12-21 19:41:08 +00:00
|
|
|
|
2015-12-21 19:46:04 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdarg.h>
|
2015-11-13 13:23:43 +00:00
|
|
|
#include <Common/path.h>
|
2015-12-06 09:59:58 +00:00
|
|
|
#include <Project64-core/N64System/SystemGlobals.h>
|
|
|
|
#include <Project64-core/N64System/Mips/TranslateVaddr.h>
|
2015-12-21 07:35:22 +00:00
|
|
|
#include <Project64-core/N64System/Mips/MemoryVirtualMem.h>
|
2021-04-14 05:34:15 +00:00
|
|
|
#include <Project64-core/N64System/N64Rom.h>
|
2015-11-13 13:23:43 +00:00
|
|
|
|
2021-04-12 11:35:39 +00:00
|
|
|
CFile * CLogging::m_hLogFile = nullptr;
|
2015-11-13 13:23:43 +00:00
|
|
|
|
|
|
|
void CLogging::Log_LW(uint32_t PC, uint32_t VAddr)
|
|
|
|
{
|
2015-12-21 07:16:29 +00:00
|
|
|
if (!GenerateLog())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (VAddr < 0xA0000000 || VAddr >= 0xC0000000)
|
|
|
|
{
|
|
|
|
uint32_t PAddr;
|
|
|
|
if (!g_TransVaddr->TranslateVaddr(VAddr, PAddr))
|
|
|
|
{
|
|
|
|
if (LogUnknown())
|
|
|
|
{
|
|
|
|
LogMessage("%08X: read from unknown ??? (%08X)", PC, VAddr);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
VAddr = PAddr + 0xA0000000;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t Value;
|
|
|
|
if (VAddr >= 0xA0000000 && VAddr < (0xA0000000 + g_MMU->RdramSize()))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (VAddr >= 0xA3F00000 && VAddr <= 0xA3F00024)
|
|
|
|
{
|
2022-02-21 09:17:14 +00:00
|
|
|
return;
|
2015-12-21 07:16:29 +00:00
|
|
|
}
|
|
|
|
if (VAddr >= 0xA4000000 && VAddr <= 0xA4001FFC)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (VAddr == 0xA4080000)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (VAddr >= 0xA4100000 && VAddr <= 0xA410001C)
|
|
|
|
{
|
2022-02-21 11:26:25 +00:00
|
|
|
return;
|
2015-12-21 07:16:29 +00:00
|
|
|
}
|
|
|
|
if (VAddr >= 0xA4300000 && VAddr <= 0xA430000C)
|
|
|
|
{
|
2022-03-04 12:23:30 +00:00
|
|
|
return;
|
2015-12-21 07:16:29 +00:00
|
|
|
}
|
|
|
|
if (VAddr >= 0xA4400000 && VAddr <= 0xA4400034)
|
|
|
|
{
|
|
|
|
if (!LogVideoInterface())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
g_MMU->LW_VAddr(VAddr, Value);
|
|
|
|
|
|
|
|
switch (VAddr)
|
|
|
|
{
|
|
|
|
case 0xA4400000: LogMessage("%08X: read from VI_STATUS_REG/VI_CONTROL_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA4400004: LogMessage("%08X: read from VI_ORIGIN_REG/VI_DRAM_ADDR_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA4400008: LogMessage("%08X: read from VI_WIDTH_REG/VI_H_WIDTH_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA440000C: LogMessage("%08X: read from VI_INTR_REG/VI_V_INTR_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA4400010: LogMessage("%08X: read from VI_CURRENT_REG/VI_V_CURRENT_LINE_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA4400014: LogMessage("%08X: read from VI_BURST_REG/VI_TIMING_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA4400018: LogMessage("%08X: read from VI_V_SYNC_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA440001C: LogMessage("%08X: read from VI_H_SYNC_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA4400020: LogMessage("%08X: read from VI_LEAP_REG/VI_H_SYNC_LEAP_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA4400024: LogMessage("%08X: read from VI_H_START_REG/VI_H_VIDEO_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA4400028: LogMessage("%08X: read from VI_V_START_REG/VI_V_VIDEO_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA440002C: LogMessage("%08X: read from VI_V_BURST_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA4400030: LogMessage("%08X: read from VI_X_SCALE_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA4400034: LogMessage("%08X: read from VI_Y_SCALE_REG (%08X)", PC, Value); return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (VAddr >= 0xA4500000 && VAddr <= 0xA4500014)
|
|
|
|
{
|
|
|
|
if (!LogAudioInterface())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
g_MMU->LW_VAddr(VAddr, Value);
|
|
|
|
|
|
|
|
switch (VAddr)
|
|
|
|
{
|
|
|
|
case 0xA4500000: LogMessage("%08X: read from AI_DRAM_ADDR_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA4500004: LogMessage("%08X: read from AI_LEN_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA4500008: LogMessage("%08X: read from AI_CONTROL_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA450000C: LogMessage("%08X: read from AI_STATUS_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA4500010: LogMessage("%08X: read from AI_DACRATE_REG (%08X)", PC, Value); return;
|
|
|
|
case 0xA4500014: LogMessage("%08X: read from AI_BITRATE_REG (%08X)", PC, Value); return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (VAddr == 0xA4800000)
|
|
|
|
{
|
|
|
|
if (!LogSerialInterface())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
g_MMU->LW_VAddr(VAddr, Value);
|
|
|
|
LogMessage("%08X: read from SI_DRAM_ADDR_REG (%08X)", PC, Value);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (VAddr == 0xA4800004)
|
|
|
|
{
|
|
|
|
if (!LogSerialInterface())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
g_MMU->LW_VAddr(VAddr, Value);
|
|
|
|
LogMessage("%08X: read from SI_PIF_ADDR_RD64B_REG (%08X)", PC, Value);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (VAddr == 0xA4800010)
|
|
|
|
{
|
|
|
|
if (!LogSerialInterface())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
g_MMU->LW_VAddr(VAddr, Value);
|
|
|
|
LogMessage("%08X: read from SI_PIF_ADDR_WR64B_REG (%08X)", PC, Value);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (VAddr == 0xA4800018)
|
|
|
|
{
|
|
|
|
if (!LogSerialInterface())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
g_MMU->LW_VAddr(VAddr, Value);
|
|
|
|
LogMessage("%08X: read from SI_STATUS_REG (%08X)", PC, Value);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (VAddr >= 0xBFC00000 && VAddr <= 0xBFC007C0)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (VAddr >= 0xBFC007C0 && VAddr <= 0xBFC007FC)
|
|
|
|
{
|
|
|
|
if (!LogPRDirectMemLoads())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
g_MMU->LW_VAddr(VAddr, Value);
|
2021-04-02 07:33:39 +00:00
|
|
|
LogMessage("%08X: read word from PIF RAM at 0x%X (%08X)", PC, VAddr - 0xBFC007C0, Value);
|
2015-12-21 07:16:29 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (VAddr >= 0xB0000040 && ((VAddr - 0xB0000000) < g_Rom->GetRomSize()))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (VAddr >= 0xB0000000 && VAddr < 0xB0000040)
|
|
|
|
{
|
|
|
|
if (!LogRomHeader())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_MMU->LW_VAddr(VAddr, Value);
|
|
|
|
switch (VAddr)
|
|
|
|
{
|
2021-04-02 07:33:39 +00:00
|
|
|
case 0xB0000004: LogMessage("%08X: read from ROM clock rate (%08X)", PC, Value); break;
|
|
|
|
case 0xB0000008: LogMessage("%08X: read from ROM boot address offset (%08X)", PC, Value); break;
|
|
|
|
case 0xB000000C: LogMessage("%08X: read from ROM release offset (%08X)", PC, Value); break;
|
|
|
|
case 0xB0000010: LogMessage("%08X: read from ROM CRC1 (%08X)", PC, Value); break;
|
|
|
|
case 0xB0000014: LogMessage("%08X: read from ROM CRC2 (%08X)", PC, Value); break;
|
|
|
|
default: LogMessage("%08X: read from ROM header 0x%X (%08X)", PC, VAddr & 0xFF, Value); break;
|
2015-12-21 07:16:29 +00:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!LogUnknown())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
LogMessage("%08X: read from unknown ??? (%08X)", PC, VAddr);
|
2015-11-13 13:23:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CLogging::Log_SW(uint32_t PC, uint32_t VAddr, uint32_t Value)
|
|
|
|
{
|
2015-12-21 07:16:29 +00:00
|
|
|
if (!GenerateLog())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (VAddr < 0xA0000000 || VAddr >= 0xC0000000)
|
|
|
|
{
|
|
|
|
uint32_t PAddr;
|
|
|
|
if (!g_TransVaddr->TranslateVaddr(VAddr, PAddr))
|
|
|
|
{
|
|
|
|
if (LogUnknown())
|
|
|
|
{
|
|
|
|
LogMessage("%08X: Writing 0x%08X to %08X", PC, Value, VAddr);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
VAddr = PAddr + 0xA0000000;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (VAddr >= 0xA0000000 && VAddr < (0xA0000000 + g_MMU->RdramSize()))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (VAddr >= 0xA3F00000 && VAddr <= 0xA3F00024)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
if (VAddr >= 0xA4000000 && VAddr <= 0xA4001FFC)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (VAddr >= 0xA4040000 && VAddr <= 0xA404001C)
|
|
|
|
{
|
2022-01-24 12:43:10 +00:00
|
|
|
return;
|
2015-12-21 07:16:29 +00:00
|
|
|
}
|
|
|
|
if (VAddr == 0xA4080000)
|
|
|
|
{
|
2022-01-24 12:43:10 +00:00
|
|
|
return;
|
2015-12-21 07:16:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (VAddr >= 0xA4100000 && VAddr <= 0xA410001C)
|
|
|
|
{
|
2022-02-21 11:26:25 +00:00
|
|
|
return;
|
2015-12-21 07:16:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (VAddr >= 0xA4200000 && VAddr <= 0xA420000C)
|
|
|
|
{
|
|
|
|
if (!LogDPSRegisters())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
switch (VAddr)
|
|
|
|
{
|
|
|
|
case 0xA4200000: LogMessage("%08X: Writing 0x%08X to DPS_TBIST_REG", PC, Value); return;
|
|
|
|
case 0xA4200004: LogMessage("%08X: Writing 0x%08X to DPS_TEST_MODE_REG", PC, Value); return;
|
|
|
|
case 0xA4200008: LogMessage("%08X: Writing 0x%08X to DPS_BUFTEST_ADDR_REG", PC, Value); return;
|
|
|
|
case 0xA420000C: LogMessage("%08X: Writing 0x%08X to DPS_BUFTEST_DATA_REG", PC, Value); return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (VAddr >= 0xA4300000 && VAddr <= 0xA430000C)
|
|
|
|
{
|
2022-03-04 12:23:30 +00:00
|
|
|
return;
|
2015-12-21 07:16:29 +00:00
|
|
|
}
|
|
|
|
if (VAddr >= 0xA4400000 && VAddr <= 0xA4400034)
|
|
|
|
{
|
|
|
|
if (!LogVideoInterface())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
switch (VAddr)
|
|
|
|
{
|
|
|
|
case 0xA4400000: LogMessage("%08X: Writing 0x%08X to VI_STATUS_REG/VI_CONTROL_REG", PC, Value); return;
|
|
|
|
case 0xA4400004: LogMessage("%08X: Writing 0x%08X to VI_ORIGIN_REG/VI_DRAM_ADDR_REG", PC, Value); return;
|
|
|
|
case 0xA4400008: LogMessage("%08X: Writing 0x%08X to VI_WIDTH_REG/VI_H_WIDTH_REG", PC, Value); return;
|
|
|
|
case 0xA440000C: LogMessage("%08X: Writing 0x%08X to VI_INTR_REG/VI_V_INTR_REG", PC, Value); return;
|
|
|
|
case 0xA4400010: LogMessage("%08X: Writing 0x%08X to VI_CURRENT_REG/VI_V_CURRENT_LINE_REG", PC, Value); return;
|
|
|
|
case 0xA4400014: LogMessage("%08X: Writing 0x%08X to VI_BURST_REG/VI_TIMING_REG", PC, Value); return;
|
|
|
|
case 0xA4400018: LogMessage("%08X: Writing 0x%08X to VI_V_SYNC_REG", PC, Value); return;
|
|
|
|
case 0xA440001C: LogMessage("%08X: Writing 0x%08X to VI_H_SYNC_REG", PC, Value); return;
|
|
|
|
case 0xA4400020: LogMessage("%08X: Writing 0x%08X to VI_LEAP_REG/VI_H_SYNC_LEAP_REG", PC, Value); return;
|
|
|
|
case 0xA4400024: LogMessage("%08X: Writing 0x%08X to VI_H_START_REG/VI_H_VIDEO_REG", PC, Value); return;
|
|
|
|
case 0xA4400028: LogMessage("%08X: Writing 0x%08X to VI_V_START_REG/VI_V_VIDEO_REG", PC, Value); return;
|
|
|
|
case 0xA440002C: LogMessage("%08X: Writing 0x%08X to VI_V_BURST_REG", PC, Value); return;
|
|
|
|
case 0xA4400030: LogMessage("%08X: Writing 0x%08X to VI_X_SCALE_REG", PC, Value); return;
|
|
|
|
case 0xA4400034: LogMessage("%08X: Writing 0x%08X to VI_Y_SCALE_REG", PC, Value); return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (VAddr >= 0xA4500000 && VAddr <= 0xA4500014)
|
|
|
|
{
|
|
|
|
if (!LogAudioInterface())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
switch (VAddr)
|
|
|
|
{
|
|
|
|
case 0xA4500000: LogMessage("%08X: Writing 0x%08X to AI_DRAM_ADDR_REG", PC, Value); return;
|
|
|
|
case 0xA4500004: LogMessage("%08X: Writing 0x%08X to AI_LEN_REG", PC, Value); return;
|
|
|
|
case 0xA4500008: LogMessage("%08X: Writing 0x%08X to AI_CONTROL_REG", PC, Value); return;
|
|
|
|
case 0xA450000C: LogMessage("%08X: Writing 0x%08X to AI_STATUS_REG", PC, Value); return;
|
|
|
|
case 0xA4500010: LogMessage("%08X: Writing 0x%08X to AI_DACRATE_REG", PC, Value); return;
|
|
|
|
case 0xA4500014: LogMessage("%08X: Writing 0x%08X to AI_BITRATE_REG", PC, Value); return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (VAddr == 0xA4800000)
|
|
|
|
{
|
|
|
|
if (!LogSerialInterface())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
LogMessage("%08X: Writing 0x%08X to SI_DRAM_ADDR_REG", PC, Value); return;
|
|
|
|
}
|
|
|
|
if (VAddr == 0xA4800004)
|
|
|
|
{
|
|
|
|
if (LogPRDMAOperations())
|
|
|
|
{
|
2021-04-02 07:33:39 +00:00
|
|
|
LogMessage("%08X: A DMA transfer from the PIF RAM has occurred", PC);
|
2015-12-21 07:16:29 +00:00
|
|
|
}
|
|
|
|
if (!LogSerialInterface())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
LogMessage("%08X: Writing 0x%08X to SI_PIF_ADDR_RD64B_REG", PC, Value); return;
|
|
|
|
}
|
|
|
|
if (VAddr == 0xA4800010)
|
|
|
|
{
|
|
|
|
if (LogPRDMAOperations())
|
|
|
|
{
|
2021-04-02 07:33:39 +00:00
|
|
|
LogMessage("%08X: A DMA transfer to the PIF RAM has occurred", PC);
|
2015-12-21 07:16:29 +00:00
|
|
|
}
|
|
|
|
if (!LogSerialInterface())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
LogMessage("%08X: Writing 0x%08X to SI_PIF_ADDR_WR64B_REG", PC, Value); return;
|
|
|
|
}
|
|
|
|
if (VAddr == 0xA4800018)
|
|
|
|
{
|
|
|
|
if (!LogSerialInterface())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
LogMessage("%08X: Writing 0x%08X to SI_STATUS_REG", PC, Value); return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (VAddr >= 0xBFC007C0 && VAddr <= 0xBFC007FC)
|
|
|
|
{
|
|
|
|
if (!LogPRDirectMemStores())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2021-04-02 07:33:39 +00:00
|
|
|
LogMessage("%08X: Writing 0x%08X to PIF RAM at 0x%X", PC, Value, VAddr - 0xBFC007C0);
|
2015-12-21 07:16:29 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!LogUnknown())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
LogMessage("%08X: Writing 0x%08X to %08X ????", PC, Value, VAddr);
|
2015-11-13 13:23:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CLogging::LogMessage(const char * Message, ...)
|
|
|
|
{
|
2015-12-21 07:16:29 +00:00
|
|
|
char Msg[400];
|
|
|
|
va_list ap;
|
2015-11-13 13:23:43 +00:00
|
|
|
|
2015-12-21 07:16:29 +00:00
|
|
|
if (!g_Settings->LoadBool(Debugger_Enabled))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2021-04-12 11:35:39 +00:00
|
|
|
if (m_hLogFile == nullptr)
|
2015-12-21 07:16:29 +00:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2015-11-13 13:23:43 +00:00
|
|
|
|
2015-12-21 07:16:29 +00:00
|
|
|
va_start(ap, Message);
|
|
|
|
vsprintf(Msg, Message, ap);
|
|
|
|
va_end(ap);
|
2015-11-13 13:23:43 +00:00
|
|
|
|
2015-12-21 07:16:29 +00:00
|
|
|
strcat(Msg, "\r\n");
|
2015-11-13 13:23:43 +00:00
|
|
|
|
2015-12-21 07:16:29 +00:00
|
|
|
m_hLogFile->Write(Msg, strlen(Msg));
|
2015-11-13 13:23:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CLogging::StartLog(void)
|
|
|
|
{
|
2015-12-21 07:16:29 +00:00
|
|
|
if (!GenerateLog())
|
|
|
|
{
|
|
|
|
StopLog();
|
|
|
|
return;
|
|
|
|
}
|
2021-04-12 11:35:39 +00:00
|
|
|
if (m_hLogFile != nullptr)
|
2015-12-21 07:16:29 +00:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-03-10 11:15:40 +00:00
|
|
|
CPath LogFile(g_Settings->LoadStringVal(Directory_Log).c_str(), "cpudebug.log");
|
2015-11-13 13:23:43 +00:00
|
|
|
m_hLogFile = new CFile(LogFile, CFileBase::modeCreate | CFileBase::modeWrite);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CLogging::StopLog(void)
|
|
|
|
{
|
2015-12-21 07:16:29 +00:00
|
|
|
if (m_hLogFile)
|
|
|
|
{
|
2015-11-13 13:23:43 +00:00
|
|
|
delete m_hLogFile;
|
2021-04-12 11:35:39 +00:00
|
|
|
m_hLogFile = nullptr;
|
2015-12-21 07:16:29 +00:00
|
|
|
}
|
2021-04-02 07:33:39 +00:00
|
|
|
}
|