forked from Ports/perfect_dark
Merge remote-tracking branch 'base/port' into port
This commit is contained in:
commit
d8c5960c80
7 files changed
+395
-20
No files matched your search
+1
-1
@@ -130,7 +130,7 @@ G_DIR := src/generated/$(ROMID)
|
||||
|
||||
ifneq (,$(findstring windows,$(TARGET_PLATFORM)))
|
||||
TARGET_CFLAGS := $(shell pkg-config sdl2 --cflags-only-I)
|
||||
TARGET_LDFLAGS := $(shell pkg-config sdl2 --libs) -lz -lopengl32 -static-libstdc++
|
||||
TARGET_LDFLAGS := $(shell pkg-config sdl2 --libs) -lz -lopengl32 -static-libstdc++ -ldbghelp
|
||||
# on windows/mingw we need this to be built with a 32-bit compiler so it finds the correct libs
|
||||
TOOLCHAIN ?= i686-w64-mingw32-
|
||||
else # assume *nix
|
||||
|
||||
@@ -31,6 +31,9 @@ void *sysMemAlloc(const u32 size);
|
||||
void *sysMemZeroAlloc(const u32 size);
|
||||
void sysMemFree(void *ptr);
|
||||
|
||||
void crashInit(void);
|
||||
void crashShutdown(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+298
-15
@@ -1,40 +1,323 @@
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE 1
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include <PR/ultratypes.h>
|
||||
#include "system.h"
|
||||
#include "platform.h"
|
||||
|
||||
#define CRASH_MAX_MSG 4096
|
||||
#define CRASH_MAX_SYM 256
|
||||
#define CRASH_MAX_FRAMES 32
|
||||
#define CRASH_MSG(...) \
|
||||
if (msglen < CRASH_MAX_MSG) msglen += snprintf(msg + msglen, CRASH_MAX_MSG - msglen, __VA_ARGS__)
|
||||
|
||||
#if defined(PLATFORM_WIN32)
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <inttypes.h>
|
||||
#include <excpt.h>
|
||||
|
||||
// NOTE: game builds with gcc, which means we have no PDBs for the windows version
|
||||
// this means that you generally won't get any symbol names in the main executable
|
||||
|
||||
static LPTOP_LEVEL_EXCEPTION_FILTER prevExFilter;
|
||||
|
||||
static void crashStackTrace(char *msg, PEXCEPTION_POINTERS exinfo)
|
||||
{
|
||||
CONTEXT context = *exinfo->ContextRecord;
|
||||
HANDLE process = GetCurrentProcess();
|
||||
HANDLE thread = GetCurrentThread();
|
||||
|
||||
SymSetOptions(SymGetOptions() | SYMOPT_DEBUG | SYMOPT_LOAD_LINES);
|
||||
SymInitialize(process, NULL, TRUE);
|
||||
|
||||
STACKFRAME64 stackframe;
|
||||
memset(&stackframe, 0, sizeof(stackframe));
|
||||
|
||||
DWORD image;
|
||||
#ifdef PLATFORM_X86
|
||||
image = IMAGE_FILE_MACHINE_I386;
|
||||
stackframe.AddrPC.Offset = context.Eip;
|
||||
stackframe.AddrPC.Mode = AddrModeFlat;
|
||||
stackframe.AddrFrame.Offset = context.Ebp;
|
||||
stackframe.AddrFrame.Mode = AddrModeFlat;
|
||||
stackframe.AddrStack.Offset = context.Esp;
|
||||
stackframe.AddrStack.Mode = AddrModeFlat;
|
||||
#elif defined(PLATFORM_X86_64)
|
||||
image = IMAGE_FILE_MACHINE_AMD64;
|
||||
stackframe.AddrPC.Offset = context.Rip;
|
||||
stackframe.AddrPC.Mode = AddrModeFlat;
|
||||
stackframe.AddrFrame.Offset = context.Rsp;
|
||||
stackframe.AddrFrame.Mode = AddrModeFlat;
|
||||
stackframe.AddrStack.Offset = context.Rsp;
|
||||
stackframe.AddrStack.Mode = AddrModeFlat;
|
||||
#else
|
||||
snprintf(msg, CRASH_MAX_MSG, "no stack trace available on this arch\n");
|
||||
return;
|
||||
#endif
|
||||
|
||||
DWORD disp = 0;
|
||||
DWORD64 disp64 = 0;
|
||||
IMAGEHLP_LINE64 line;
|
||||
DWORD msglen = 0;
|
||||
|
||||
CRASH_MSG("EXCEPTION: 0x%08lx\n", exinfo->ExceptionRecord->ExceptionCode);
|
||||
CRASH_MSG("PC: %p", exinfo->ExceptionRecord->ExceptionAddress);
|
||||
if (SymGetLineFromAddr64(process, (uintptr_t)exinfo->ExceptionRecord->ExceptionAddress, &disp, &line)) {
|
||||
CRASH_MSG(": %s:%lu+%lu", line.FileName, line.LineNumber, disp);
|
||||
}
|
||||
CRASH_MSG("\n\nBACKTRACE:\n");
|
||||
|
||||
char symbuf[sizeof(SYMBOL_INFO) + CRASH_MAX_SYM * sizeof(TCHAR)];
|
||||
PSYMBOL_INFO sym = (PSYMBOL_INFO)symbuf;
|
||||
sym->SizeOfStruct = sizeof(*sym);
|
||||
sym->MaxNameLen = CRASH_MAX_SYM;
|
||||
|
||||
s32 i;
|
||||
for (i = 0; i < CRASH_MAX_FRAMES; ++i) {
|
||||
const BOOL res = StackWalk64(image, process, thread, &stackframe, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL);
|
||||
if (!res) {
|
||||
break;
|
||||
}
|
||||
|
||||
CRASH_MSG("#%02d: %p", i, (void *)(uintptr_t)stackframe.AddrPC.Offset);
|
||||
|
||||
if (SymFromAddr(process, stackframe.AddrPC.Offset, &disp64, sym)) {
|
||||
CRASH_MSG(": %s+%llu", sym->Name, disp64);
|
||||
}
|
||||
|
||||
if(SymGetLineFromAddr64(process, stackframe.AddrPC.Offset, &disp, &line)) {
|
||||
CRASH_MSG(" (%s:%lu+%lu)", line.FileName, line.LineNumber, disp);
|
||||
}
|
||||
|
||||
CRASH_MSG("\n");
|
||||
}
|
||||
|
||||
if (i <= 1) {
|
||||
CRASH_MSG("no information\n");
|
||||
} else if (i == CRASH_MAX_FRAMES) {
|
||||
CRASH_MSG("...\n");
|
||||
}
|
||||
|
||||
SymCleanup(process);
|
||||
}
|
||||
|
||||
static long __stdcall crashHandler(PEXCEPTION_POINTERS exinfo)
|
||||
{
|
||||
char msg[CRASH_MAX_MSG + 1] = { 0 };
|
||||
|
||||
if (IsDebuggerPresent()) {
|
||||
if (prevExFilter) {
|
||||
return prevExFilter(exinfo);
|
||||
}
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
sysLogPrintf(LOG_ERROR, "FATAL: Crashed: PC=%p CODE=0x%08lx", exinfo->ExceptionRecord->ExceptionAddress, exinfo->ExceptionRecord->ExceptionCode);
|
||||
|
||||
fflush(stderr);
|
||||
fflush(stdout);
|
||||
|
||||
crashStackTrace(msg, exinfo);
|
||||
|
||||
sysFatalError("Crash!\n\n%s", msg);
|
||||
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
#elif defined(PLATFORM_LINUX)
|
||||
|
||||
#include <ucontext.h>
|
||||
#include <signal.h>
|
||||
#include <execinfo.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/fcntl.h>
|
||||
|
||||
static struct sigaction prevSigAction;
|
||||
|
||||
static s32 crashIsDebuggerPresent(void)
|
||||
{
|
||||
static s32 result = -1;
|
||||
|
||||
if (result >= 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
char buf[4096] = { 0 };
|
||||
|
||||
int fd = open("/proc/self/status", O_RDONLY);
|
||||
if (fd < 0) {
|
||||
result = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rx = read(fd, buf, sizeof(buf) - 1);
|
||||
close(fd);
|
||||
if (rx <= 0) {
|
||||
result = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
buf[rx] = 0;
|
||||
|
||||
char *str = strstr(buf, "TracerPid:");
|
||||
if (!str) {
|
||||
result = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
str += 10;
|
||||
|
||||
while (*str && !isdigit(*str)) {
|
||||
if (*str == '\n') {
|
||||
result = 0;
|
||||
return 0;
|
||||
}
|
||||
++str;
|
||||
}
|
||||
|
||||
result = (atoi(str) != 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void crashStackTrace(char *msg, s32 sig, void *pc)
|
||||
{
|
||||
u32 msglen = 0;
|
||||
void *frames[CRASH_MAX_FRAMES] = { NULL };
|
||||
|
||||
const s32 nframes = backtrace(frames, CRASH_MAX_FRAMES);
|
||||
if (nframes <= 0) {
|
||||
CRASH_MSG("no information\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char **strings = backtrace_symbols(frames, nframes);
|
||||
|
||||
CRASH_MSG("SIGNAL: %d\n", sig);
|
||||
CRASH_MSG("PC: ");
|
||||
if (pc) {
|
||||
CRASH_MSG("%p\n", pc);
|
||||
} else if (strings) {
|
||||
CRASH_MSG("%s\n", strings[0]);
|
||||
} else {
|
||||
CRASH_MSG("%p\n", frames[0]);
|
||||
}
|
||||
|
||||
CRASH_MSG("\nBACKTRACE:\n");
|
||||
|
||||
s32 i;
|
||||
for (i = 0; i < nframes; ++i) {
|
||||
CRASH_MSG("#%02d: ", i);
|
||||
if (strings && strings[i]) {
|
||||
CRASH_MSG("%s\n", strings[i]);
|
||||
} else {
|
||||
CRASH_MSG("%p\n", frames[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (i == CRASH_MAX_FRAMES) {
|
||||
CRASH_MSG("...\n");
|
||||
} else if (i <= 1) {
|
||||
CRASH_MSG("no information\n");
|
||||
}
|
||||
|
||||
free(strings);
|
||||
}
|
||||
|
||||
static void crashHandler(s32 sig, siginfo_t *siginfo, void *ctx)
|
||||
{
|
||||
char msg[CRASH_MAX_MSG + 1] = { 0 };
|
||||
|
||||
if (crashIsDebuggerPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
void *pc = NULL;
|
||||
if (ctx) {
|
||||
ucontext_t *ucontext = (ucontext_t *)ctx;
|
||||
#ifdef PLATFORM_X86
|
||||
pc = (void *)ucontext->uc_mcontext.gregs[REG_EIP];
|
||||
#elif defined(PLATFORM_X86_64)
|
||||
pc = (void *)ucontext->uc_mcontext.gregs[REG_RIP];
|
||||
#elif defined(PLATFORM_ARM) && defined(PLATFORM_64BIT)
|
||||
pc = (void *)ucontext->uc_mcontext.pc;
|
||||
#elif defined(PLATFORM_ARM)
|
||||
pc = (void *)ucontext->uc_mcontext.arm_pc;
|
||||
#endif
|
||||
}
|
||||
|
||||
sysLogPrintf(LOG_ERROR, "FATAL: Crashed: PC=%p SIGNAL=%d", pc, sig);
|
||||
|
||||
fflush(stderr);
|
||||
fflush(stdout);
|
||||
|
||||
crashStackTrace(msg, sig, pc);
|
||||
|
||||
sysFatalError("Crash!\n\n%s", msg);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
u8 g_CrashHasMessage = 0;
|
||||
s32 g_CrashEnabled = 0;
|
||||
|
||||
static char crashMsg[1024];
|
||||
|
||||
void crashInit(void)
|
||||
{
|
||||
#ifdef PLATFORM_WIN32
|
||||
SetErrorMode(SEM_FAILCRITICALERRORS);
|
||||
prevExFilter = SetUnhandledExceptionFilter(crashHandler);
|
||||
g_CrashEnabled = 1;
|
||||
#elif defined(PLATFORM_LINUX)
|
||||
struct sigaction sigact = { 0 };
|
||||
sigact.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||
sigact.sa_sigaction = crashHandler;
|
||||
sigaction(SIGSEGV, &sigact, &prevSigAction);
|
||||
sigaction(SIGABRT, &sigact, &prevSigAction);
|
||||
sigaction(SIGBUS, &sigact, &prevSigAction);
|
||||
sigaction(SIGILL, &sigact, &prevSigAction);
|
||||
g_CrashEnabled = 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void crashShutdown(void)
|
||||
{
|
||||
#ifdef PLATFORM_WIN32
|
||||
if (prevExFilter) {
|
||||
SetUnhandledExceptionFilter(prevExFilter);
|
||||
}
|
||||
#elif defined(PLATFORM_LINUX)
|
||||
sigaction(SIGSEGV, &prevSigAction, NULL);
|
||||
sigaction(SIGABRT, &prevSigAction, NULL);
|
||||
sigaction(SIGBUS, &prevSigAction, NULL);
|
||||
sigaction(SIGILL, &prevSigAction, NULL);
|
||||
#endif
|
||||
g_CrashEnabled = 0;
|
||||
}
|
||||
|
||||
void crashCreateThread(void)
|
||||
{
|
||||
// TODO: install a crash handler of some sort
|
||||
|
||||
}
|
||||
|
||||
void crashSetMessage(char *string)
|
||||
{
|
||||
strncpy(crashMsg, string, sizeof(crashMsg) - 1);
|
||||
sysLogPrintf(LOG_ERROR, "crashSetMessage: %s", crashMsg);
|
||||
g_CrashHasMessage = 1;
|
||||
|
||||
}
|
||||
|
||||
void crashReset(void)
|
||||
{
|
||||
crashMsg[0] = 0;
|
||||
g_CrashHasMessage = 0;
|
||||
|
||||
}
|
||||
|
||||
void crashAppendChar(char c)
|
||||
{
|
||||
g_CrashHasMessage = 1;
|
||||
for (u32 i = 0; i < sizeof(crashMsg) - 1; ++i) {
|
||||
if (crashMsg[i] == '\0') {
|
||||
crashMsg[i] = c;
|
||||
crashMsg[i + 1] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -60,6 +60,7 @@ static void cleanup(void)
|
||||
sysLogPrintf(LOG_NOTE, "shutdown");
|
||||
inputSaveConfig();
|
||||
configSave(CONFIG_FNAME);
|
||||
crashShutdown();
|
||||
// TODO: actually shut down all subsystems
|
||||
}
|
||||
|
||||
@@ -75,6 +76,7 @@ static void gameLoadConfig(void)
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
crashInit();
|
||||
sysInit(argc, argv);
|
||||
fsInit();
|
||||
configInit();
|
||||
|
||||
@@ -112,8 +112,16 @@ void sysLogPrintf(s32 level, const char *fmt, ...)
|
||||
|
||||
void sysFatalError(const char *fmt, ...)
|
||||
{
|
||||
static s32 alreadyCrashed = 0;
|
||||
|
||||
if (alreadyCrashed) {
|
||||
abort();
|
||||
}
|
||||
|
||||
char errmsg[2048] = { 0 };
|
||||
|
||||
alreadyCrashed = 1;
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(errmsg, sizeof(errmsg), fmt, ap);
|
||||
|
||||
+11
-4
@@ -1295,12 +1295,19 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i
|
||||
movedata.aimturnleftspeed = (g_Vars.currentplayer->swivelpos[0] - -0.9f) / -0.1f;
|
||||
movedata.aimturnrightspeed = 0.f;
|
||||
}
|
||||
f32 vertaup = 0.f, vertadown = 0.f;
|
||||
if (g_Vars.currentplayer->swivelpos[1] > 0.9f) {
|
||||
movedata.speedvertaup = (g_Vars.currentplayer->swivelpos[1] - 0.9f) / 0.1f;
|
||||
movedata.speedvertadown = 0.f;
|
||||
vertaup = (g_Vars.currentplayer->swivelpos[1] - 0.9f) / 0.1f;
|
||||
} else if (g_Vars.currentplayer->swivelpos[1] < -0.9f) {
|
||||
movedata.speedvertadown = (g_Vars.currentplayer->swivelpos[1] - -0.9f) / -0.1f;
|
||||
movedata.speedvertaup = 0.f;
|
||||
vertadown = (g_Vars.currentplayer->swivelpos[1] - -0.9f) / -0.1f;
|
||||
}
|
||||
// Uninvert pitch if needed
|
||||
if (movedata.invertpitch) {
|
||||
movedata.speedvertaup = vertadown;
|
||||
movedata.speedvertadown = vertaup;
|
||||
} else {
|
||||
movedata.speedvertaup = vertaup;
|
||||
movedata.speedvertadown = vertadown;
|
||||
}
|
||||
} else {
|
||||
// Reset mouse aim position when not mouse aiming
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "data.h"
|
||||
#include "types.h"
|
||||
#ifndef PLATFORM_N64
|
||||
#include "game/gfxmemory.h"
|
||||
#include "game/game_13c510.h"
|
||||
#include "game/player.h"
|
||||
#endif
|
||||
@@ -826,6 +827,7 @@ Gfx *skyRender(Gfx *gdl)
|
||||
|
||||
gDPSetRenderMode(gdl++, G_RM_OPA_SURF, G_RM_OPA_SURF2);
|
||||
|
||||
#ifdef PLATFORM_N64
|
||||
if (numvertices == 4) {
|
||||
gdl = skyRenderTri(gdl, &watervertices2d[0], &watervertices2d[1], &watervertices2d[3], 130.0f, true);
|
||||
|
||||
@@ -846,6 +848,40 @@ Gfx *skyRender(Gfx *gdl)
|
||||
// 1 corner is on the ground
|
||||
gdl = skyRenderTri(gdl, &watervertices2d[0], &watervertices2d[1], &watervertices2d[2], 130.0f, true);
|
||||
}
|
||||
#else
|
||||
Vtx *verts = gfxAllocateVertices(5);
|
||||
Col *cols = gfxAllocateColours(5);
|
||||
Mtxf *mtx = gfxAllocateMatrix();
|
||||
mtx4MultMtx4(camGetWorldToScreenMtxf(), &g_SkyMtx, mtx);
|
||||
mtxF2L(mtx, mtx);
|
||||
|
||||
gSPMatrix(gdl++, osVirtualToPhysical(mtx), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_PUSH);
|
||||
gSPColor(gdl++, osVirtualToPhysical(cols), numvertices);
|
||||
gSPVertex(gdl++, osVirtualToPhysical(verts), numvertices, 0);
|
||||
|
||||
for (s32 i = 0; i < numvertices; ++i) {
|
||||
verts[i].x = watervertices3d[i].x;
|
||||
verts[i].y = watervertices3d[i].y;
|
||||
verts[i].z = watervertices3d[i].z;
|
||||
verts[i].s = watervertices3d[i].s;
|
||||
verts[i].t = watervertices3d[i].t;
|
||||
verts[i].colour = i * 4;
|
||||
cols[i].r = watervertices3d[i].r;
|
||||
cols[i].g = watervertices3d[i].g;
|
||||
cols[i].b = watervertices3d[i].b;
|
||||
cols[i].a = watervertices3d[i].a;
|
||||
}
|
||||
|
||||
if (numvertices == 4) {
|
||||
gSPTri2(gdl++, 0, 1, 3, 3, 2, 0);
|
||||
} else if (numvertices == 5) {
|
||||
gSPTri3(gdl++, 0, 1, 2, 0, 2, 3, 0, 3, 4);
|
||||
} else if (numvertices == 3) {
|
||||
gSPTri1(gdl++, 0, 1, 2);
|
||||
}
|
||||
|
||||
gSPPopMatrix(gdl++, G_MTX_MODELVIEW);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1252,6 +1288,7 @@ Gfx *skyRender(Gfx *gdl)
|
||||
skyvertices2d[i].y = skyClamp(skyvertices2d[i].y, camGetScreenTop() * 4.0f, (camGetScreenTop() + camGetScreenHeight()) * 4.0f - 1.0f);
|
||||
}
|
||||
|
||||
#ifdef PLATFORM_N64
|
||||
if (numvertices == 4) {
|
||||
if (cornerstate == CORNERSTATE_TOP) {
|
||||
if (sp548 < sp54c) {
|
||||
@@ -1292,6 +1329,41 @@ Gfx *skyRender(Gfx *gdl)
|
||||
// One corner is in the sky
|
||||
gdl = skyRenderTri(gdl, &skyvertices2d[0], &skyvertices2d[1], &skyvertices2d[2], 130.0f, true);
|
||||
}
|
||||
#else
|
||||
Vtx *verts = gfxAllocateVertices(5);
|
||||
Col *cols = gfxAllocateColours(5);
|
||||
Mtxf *mtx = gfxAllocateMatrix();
|
||||
mtx4MultMtx4(camGetWorldToScreenMtxf(), &g_SkyMtx, mtx);
|
||||
mtxF2L(mtx, mtx);
|
||||
|
||||
gSPMatrix(gdl++, osVirtualToPhysical(mtx), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_PUSH);
|
||||
gSPColor(gdl++, osVirtualToPhysical(cols), numvertices);
|
||||
gSPVertex(gdl++, osVirtualToPhysical(verts), numvertices, 0);
|
||||
|
||||
const f32 iscale = 1.f / scale;
|
||||
for (s32 i = 0; i < numvertices; ++i) {
|
||||
verts[i].x = skyvertices3d[i].x;
|
||||
verts[i].y = skyvertices3d[i].y;
|
||||
verts[i].z = skyvertices3d[i].z;
|
||||
verts[i].s = skyvertices3d[i].s;
|
||||
verts[i].t = skyvertices3d[i].t;
|
||||
verts[i].colour = i * 4;
|
||||
cols[i].r = skyvertices3d[i].r;
|
||||
cols[i].g = skyvertices3d[i].g;
|
||||
cols[i].b = skyvertices3d[i].b;
|
||||
cols[i].a = skyvertices3d[i].a;
|
||||
}
|
||||
|
||||
if (numvertices == 4) {
|
||||
gSPTri2(gdl++, 0, 1, 3, 3, 2, 0);
|
||||
} else if (numvertices == 5) {
|
||||
gSPTri3(gdl++, 0, 1, 2, 0, 2, 3, 0, 3, 4);
|
||||
} else if (numvertices == 3) {
|
||||
gSPTri1(gdl++, 0, 1, 2);
|
||||
}
|
||||
|
||||
gSPPopMatrix(gdl++, G_MTX_MODELVIEW);
|
||||
#endif
|
||||
}
|
||||
|
||||
return gdl;
|
||||
|
||||
Reference in new issue
Block a user