/*
**
** dminit.c
**
** Debug module initialization
**
*/

#include "dmp.h"
#include <xboxp.h>
#include "xconfig.h"
#include <xprofp.h>

BOOL fBootWait; // 2 means wait forever, 3 means wait once with no timeout
PKPROCESS pprocSystem;
DMINIT g_dmi;
DMGD g_dmgd;
DMXAPI g_dmxapi;
char rgchTitleDir[MAX_OBJ_PATH+1];
char rgchTitleName[64];
char *pszCmdLine;
RTL_CRITICAL_SECTION csIniFile;
HANDLE g_hIniFile;
BOOL g_fFinishedLoadingIniFile;
HAL_SHUTDOWN_REGISTRATION hsrAPIC = { DisableAPIC, 0 };
ULONG g_ulDbgIP;
KEVENT kevtNull;

// This is what we're calling our ini file for now.  It will change someday
char szIniName[] = "E:\\xbdm.ini";
char szDxtDir[] = "E:\\dxt";
OCHAR oszDxtTemplate[] = OTEXT("*.DXT");

VOID CreateThreadNotify(HANDLE pid, HANDLE tid, BOOLEAN fCreate);
void LoadIniFile(void);
void LoadPlugIns(void);

ULONG DmEntryPoint(PVOID pvXbdmBase, PDMINIT pdmi, ULONG unused)
{
    WSADATA wsad;
    NTSTATUS st;
    KIRQL irqlSav;

    // Pass a pointer to the kernel's global profile data structure
    DmProfileData = (XProfpGlobals*)pdmi->XProfpDataPtr;

    /* Finish the process of fixing up our image */
    if(!FFixupXbdm(pvXbdmBase, pdmi))
        return STATUS_DRIVER_ENTRYPOINT_NOT_FOUND;

	ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
    /* Set things up */
    if(!FInitThreadDebugData(PsGetCurrentThread()))
        return STATUS_NO_MEMORY;
	pprocSystem = PsGetCurrentProcess();
	g_dmi = *pdmi;
    g_dmi.D3DDriverData = &g_dmgd;
    g_dmi.XapiData = &g_dmxapi;
	InitializeCriticalSection(&csIniFile);
    HalRegisterShutdownNotification(&hsrAPIC, TRUE);
    KeInitializeEvent(&kevtNull, SynchronizationEvent, TRUE);
    InitSecurity();
    InitBreakpoints();
    InitNotifications();
	InitServ();
    InitLoader();
    InitCounters();
	KeGetCurrentPrcb()->DebugMonitorData = &g_dmi;
	dwExecState = DMN_EXEC_PENDING;

    // register for thread notifications (so we can keep our per-thread data)
    st = PsSetCreateThreadNotifyRoutine(CreateThreadNotify);
    if(!NT_SUCCESS(st)) {
        ASSERT(FALSE);
        return st;
    }

    /* If we've loaded from CD, mark our paths correctly */
    if(pdmi->Flags & DMIFLAG_CDBOOT)
        szIniName[0] = szDxtDir[0] = 1;

    // Load the ini file
    LoadIniFile();
	
    // start networking
    st = XNetStartup(NULL);
    ASSERT(st == NO_ERROR);

	// start winsock
    if(0 != WSAStartup(2, &wsad))
        return STATUS_NO_MEMORY;

    /* Start our listening thread */
    if(!FStartDebugService())
        return STATUS_NO_MEMORY;

    /* Reconnect persistent notification channels.  We're not necessarily
     * ready to start getting commands from them, but we won't give them any
     * reason to do anything yet */
	EnsurePersistentSockets();

    /* Load all plugins */
    LoadPlugIns();

    /* If we're in an active debug session, we need to wait until we've
     * reconnected with the host */
    if(fBootWait && DmGetCurrentDmtd()->DebugEvent) {
		LARGE_INTEGER li;
        BOOL fWait = fBootWait;

		DbgPrint("dm: waiting for debugger connection\n");
		if(fBootWait != 2) {
			/* One wait is all we'll do */
			fBootWait = FALSE;
			WriteIniFile();
		}
		DmGetCurrentDmtd()->DebugFlags |= DMFLAG_STOPPED;
		NotifyComponents(DM_EXEC, dwExecState = DMN_EXEC_PENDING);
		/* We'll sit in a wait state for only fifteen seconds unless we've been
		 * told to wait forever */
		li.QuadPart = Int32x32To64(15000, -10000);
        st = KeWaitForSingleObject(DmGetCurrentDmtd()->DebugEvent, UserRequest,
            UserMode, FALSE, fWait == 1 ? &li : NULL);
		if(st == STATUS_TIMEOUT)
			NotifyComponents(DM_EXEC, dwExecState = DMN_EXEC_START);
		DmGetCurrentDmtd()->DebugFlags &= ~(DMFLAG_STOPPED | DMFLAG_EXCEPTION);
	} else
		NotifyComponents(DM_EXEC, dwExecState = DMN_EXEC_START);

	/* If we have a specified title directory, let's ensure it's a valid
	 * directory and then map it in */
	FTitleExists(*rgchTitleDir ? rgchTitleDir : 0, rgchTitleName, pszCmdLine,
		TRUE);
    irqlSav = KeRaiseIrqlToDpcLevel();
    if(pszCmdLine) {
        DmFreePool(pszCmdLine);
        pszCmdLine = NULL;
    }
    KeLowerIrql(irqlSav);

	*pdmi = g_dmi;
	return STATUS_SUCCESS;
}

VOID CreateThreadNotify(HANDLE pid, HANDLE tid, BOOLEAN fCreate)
{
    if(fCreate) {
		NTSTATUS st;
		PETHREAD pthr;

		st = PsLookupThreadByThreadId(tid, &pthr);
		if(NT_SUCCESS(st)) {
			if(!FInitThreadDebugData(pthr))
				DbgPrint("dm: could not alloc thread data\n");
			else if(pthr->Tcb.TlsData) {
				/* We want to advertise this thread's creation, but we need to
				 * do that within the context of the created thread, not our
				 * thread, so we replace the thread's system routine with our
				 * own, which will send the appropriate notifications */
				PDMTD pdmtd = (PDMTD)pthr->DebugData;
				PKSWITCHFRAME pksf = (PKSWITCHFRAME)pthr->Tcb.KernelStack;
				PKSYSTEM_ROUTINE *ppfn = (PVOID)(pksf + 1);

				pdmtd->DebugFlags |= DMFLAG_STARTTHREAD;
				pdmtd->SystemStartupRoutine = *ppfn;
				*ppfn = DmpThreadStartup;
			}
            ObDereferenceObject(pthr);
		} else
			DbgPrint("dm: could not get new thread\n");
    } else {
	    DMTD *pdmtd;

		/* Advertise this thread's destruction */
		if(PsGetCurrentThread()->Tcb.TlsData)
			NotifyComponents(DM_DESTROYTHREAD,
				(DWORD)PsGetCurrentThread()->UniqueThread);
        pdmtd = PsGetCurrentThread()->DebugData;
		if(pdmtd) {
			PsGetCurrentThread()->DebugData = NULL;
            if(pdmtd->ExceptionStack) {
                MmDeleteKernelStack(pdmtd->ExceptionStack,
                    (PUCHAR)pdmtd->ExceptionStack - 0x2000);
            }
			if(pdmtd->DebugEvent) {
				pdmtd->DebugEvent = NULL;
				/* There shouldn't be anybody waiting on this event */
				ASSERT(IsListEmpty(&pdmtd->DebugEventData.Header.WaitListHead));
			}
			DmFreePool(pdmtd);
		}
    }
}

VOID SetIPParam(ULONG ulValueIndex, LPCSTR pszStaticIP)
{
	ULONG ulAddr = htonl(UlAddrFromSz(pszStaticIP));

	ExSaveNonVolatileSetting(ulValueIndex, REG_DWORD, &ulAddr, sizeof(ulAddr));
}

BOOL FInitThreadDebugData(PETHREAD pthr)
{
    PDMTD pdmtd;

    /* Allocate a new thread data structure, if we don't already have one */
	pdmtd = pthr->DebugData;
	if(!pdmtd) {
		pdmtd = pthr->DebugData = DmAllocatePoolWithTag(sizeof(DMTD), 'dtmD');
		if(!pdmtd)
			return FALSE;
		RtlZeroMemory(pdmtd, sizeof(DMTD));

        /* Allocate an 8k stack on which we can process exceptions.  If this
         * allocation fails, we'll run exceptions on the thread's stack and
         * hope we don't run out of stack */
        pdmtd->ExceptionStack = MmCreateKernelStack(0x2000, TRUE);
	}

    /* Mark this as an XAPI thread */
	if(pthr->Tcb.TlsData)
		pdmtd->DebugFlags |= DMFLAG_XAPITHREAD;

    /* Set up a synchronization event for exception notification */
    if(pdmtd->DebugEvent) {
        ASSERT(FALSE);
        return TRUE;
    }
    KeInitializeEvent(&pdmtd->DebugEventData, NotificationEvent, FALSE);
	pdmtd->DebugEvent = &pdmtd->DebugEventData;
    return TRUE;
}

void DoIniLine(LPCSTR sz)
{
	char rgchIPAddr[20];
	int cchCmd = CchOfWord(sz);

	if(cchCmd == 0); // nothing to do
    else if(FEqualRgch("wait", sz, cchCmd)) {
        if(PchGetParam(sz, "forever", FALSE))
            fBootWait = 2;
        else if(PchGetParam(sz, "stop", FALSE))
            fBootWait = 3;
        else
            fBootWait = 1;
    } else if(FEqualRgch("notify", sz, cchCmd)) {
		/* Set up a notification channel here */
	} else if(FEqualRgch("notifyat", sz, cchCmd)) {
		FNotifyAtCmd(INVALID_SOCKET, sz, TRUE);
	} else if(FEqualRgch("staticip", sz, cchCmd)) {
        FGetDwParam(sz, "addr", &g_ulDbgIP);
#if 0
    // deprecated network commands
	} else if(FEqualRgch("staticip", sz, cchCmd)) {
		if(!FGetSzParam(sz, "addr", rgchIPAddr, sizeof rgchIPAddr)) {
			*rgchIPAddr = 0;
		}
		SetIPParam(XC_ONLINE_IP_ADDRESS, rgchIPAddr);
	} else if(FEqualRgch("subnetmask", sz, cchCmd)) {
		if(!FGetSzParam(sz, "addr", rgchIPAddr, sizeof rgchIPAddr)) {
			*rgchIPAddr = 0;
		}
		SetIPParam(XC_ONLINE_SUBNET_ADDRESS, rgchIPAddr);
	} else if(FEqualRgch("defgateway", sz, cchCmd)) {
		if(!FGetSzParam(sz, "addr", rgchIPAddr, sizeof rgchIPAddr)) {
			*rgchIPAddr = 0;
		}
		SetIPParam(XC_ONLINE_DEFAULT_GATEWAY_ADDRESS, rgchIPAddr);
#endif
	} else
		/* Process this as a normal command if we can */
		HandleCommand(-1, sz);
}

void LoadIniFile(void)
{
	NTSTATUS st;
	IO_STATUS_BLOCK iosb;
	char *rgchBuf;
	char *pch;
	char *pchLine;
	int cb;
	const int cbBuf = 0x1000;

	/* Make sure we can get a read buffer */
	rgchBuf = DmAllocatePoolWithTag(cbBuf + 1, 'mdbX');
	if(!rgchBuf)
		return;

	RtlEnterCriticalSection(&csIniFile);

    /* We leave the ini file open so that we can keep a reference to the FCB
     * and restrict access to the ini file if somebody tries to copy it */
    st = FCreateFile(&g_hIniFile, GENERIC_READ | SYNCHRONIZE, szIniName,
        NULL, 0, 0, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT);

	if(NT_SUCCESS(st)) {
		/* Repeatedly read in chunks of file until there's no more file */
		cb = 0;
		for(;;) {
			st = NtReadFile(g_hIniFile, NULL, NULL, NULL, &iosb, rgchBuf + cb,
				cbBuf - cb, NULL);
            if(NT_SUCCESS(st))
				cb += iosb.Information;
			if(!cb)
				break;
			/* Keep pulling lines out of this block to process */
			pchLine = rgchBuf;
			for(pch = rgchBuf; pch < rgchBuf + cb; ++pch) {
				if(*pch == '\012' || *pch == '\015') {
					*pch = 0;
					DoIniLine(pchLine);
					pchLine = pch + 1;
				}
			}
			/* If we've miraculously failed to hit any line breaks in the
			 * data we have, we'll treat the whole thing as one big line */
			if(pchLine == rgchBuf) {
				*pch = 0;
				DoIniLine(pchLine);
				cb = 0;
			} else {
				/* We've processed a bunch of lines and have a partial line
				 * left over, so set it aside and get ready to read more
				 * file */
				memmove(rgchBuf, pchLine, rgchBuf + cb - pchLine);
				cb -= pchLine - rgchBuf;
			}
		}
	}
    g_fFinishedLoadingIniFile = TRUE;
	RtlLeaveCriticalSection(&csIniFile);
	DmFreePool(rgchBuf);
}

#if 0
void LoadPlugIn(const OCHAR *oszName)
{
	PVOID pvImage;
    HANDLE h;

    if(SUCCEEDED(DmLoadExtension(oszName, &h, &pvImage))) {
		PDM_ENTRYPROC pfn;

		pfn = (PDM_ENTRYPROC)((ULONG_PTR)pvImage +
			RtlImageNtHeader(pvImage)->OptionalHeader.AddressOfEntryPoint);
		//_asm int 3
		pfn();
	}
}
#endif

void LoadPlugIns(void)
{
	HANDLE h;
	NTSTATUS st;
	IO_STATUS_BLOCK iosb;
	FILE_DIRECTORY_INFORMATION fda;
	OCHAR oszName[256];
    struct
    {
	    FILE_DIRECTORY_INFORMATION fna;
        OCHAR ozName[256];
    } fna;
	BOOLEAN fFirst = TRUE;
    OBJECT_STRING templateName;

	st = FCreateFile(&h, FILE_LIST_DIRECTORY | SYNCHRONIZE, szDxtDir, NULL,
		0, FILE_SHARE_READ, FILE_OPEN, FILE_DIRECTORY_FILE |
		FILE_SYNCHRONOUS_IO_NONALERT);
	if(NT_SUCCESS(st)) {
        templateName.Buffer = oszDxtTemplate;
        templateName.Length = sizeof oszDxtTemplate - sizeof(OCHAR);
        templateName.MaximumLength = sizeof oszDxtTemplate;

		do {
			st = NtQueryDirectoryFile(h, NULL, NULL, NULL, &iosb, &fna.fna,
				sizeof fna, FileDirectoryInformation, &templateName, fFirst);
			if(NT_SUCCESS(st)) {
				int cch = fna.fna.FileNameLength / sizeof(OCHAR);
				soprintf(oszName, OTEXT("%s\\%*.*s"),
					szDxtDir, cch, cch, fna.fna.FileName);
                DmLoadExtension(oszName, NULL, NULL);
			}
			fFirst = FALSE;
		} while (NT_SUCCESS(st));
		NtClose(h);
	}
}

void WriteIniData(INF *pinf, const BYTE *pb, int cb)
{
	IO_STATUS_BLOCK iosb;

	if(pb) {
		while(cb) {
			int cbT = pinf->cbBuf - pinf->cbUsed;
			if(cb >= cbT) {
				memcpy(pinf->pbBuf + pinf->cbUsed, pb, cbT);
				NtWriteFile(pinf->h, NULL, NULL, NULL, &iosb, pinf->pbBuf,
					pinf->cbBuf, NULL);
				pb += cbT;
				cb -= cbT;
				pinf->cbUsed = 0;
			} else {
				memcpy(pinf->pbBuf + pinf->cbUsed, pb, cb);
				pb += cb;
				pinf->cbUsed += cb;
				cb = 0;
			}
		}
	} else {
		/* Flush */
		NtWriteFile(pinf->h, NULL, NULL, NULL, &iosb, pinf->pbBuf,
			pinf->cbUsed, NULL);
		pinf->cbUsed = 0;
	}
}

void WriteIniSzNoCRLF(INF *pinf, LPCSTR sz)
{
	WriteIniData(pinf, sz, strlen(sz));
}

void WriteIniSz(INF *pinf, LPCSTR sz)
{
	WriteIniSzNoCRLF(pinf, sz);
	WriteIniData(pinf, "\015\012", 2);
}

void WriteIniFile(void)
{
	NTSTATUS st;
	INF inf;
    char sz[64];

    /* We don't write if we're a CD boot.  We also don't write if we haven't
     * finished reading the ini file yet */
    if((g_dmi.Flags & DMIFLAG_CDBOOT) || !g_fFinishedLoadingIniFile)
        return;

	inf.cbBuf = 0x1000;
	inf.cbUsed = 0;
	/* Make sure we can get a write buffer */
	inf.pbBuf = DmAllocatePoolWithTag(inf.cbBuf, 'mdbX');
	if(!inf.pbBuf)
		return;

	RtlEnterCriticalSection(&csIniFile);
    if(g_hIniFile)
        NtClose(g_hIniFile);

    st = FCreateFile(&inf.h, GENERIC_WRITE | SYNCHRONIZE, szIniName, NULL,
        0, 0, FILE_OVERWRITE_IF, FILE_SYNCHRONOUS_IO_NONALERT);
	if(NT_SUCCESS(st)) {
        g_hIniFile = inf.h;
		WriteIniSz(&inf, "[xbdm]");
		/* Write all the data we know about */
        switch(fBootWait) {
        case 1:
            WriteIniSz(&inf, "wait");
            break;
        case 2:
            WriteIniSz(&inf, "wait forever");
            break;
        case 3:
            WriteIniSz(&inf, "wait stop");
            break;
        }
#if 0
		/* For now, titledir is not reboot persistent */
		if(rgchTitleDir[0]) {
			WriteIniSzNoCRLF(&inf, "titledir name=");
			WriteIniSz(&inf, rgchTitleDir);
		}
#endif
		if(rgchDbgName[0]) {
			WriteIniSzNoCRLF(&inf, "dbgname name=");
			WriteIniSz(&inf, rgchDbgName);
		}
        if(g_ulDbgIP) {
            sprintf(sz, "staticip addr=0x%08x", g_ulDbgIP);
            WriteIniSz(&inf, sz);
        }
		WriteIniNotifiers(&inf);
        WriteIniSecurity(&inf);
		/* Flush */
		WriteIniData(&inf, NULL, 0);
    } else
        g_hIniFile = NULL;

	RtlLeaveCriticalSection(&csIniFile);
	DmFreePool(inf.pbBuf);
}

__declspec(naked) void DisableAPIC(PHAL_SHUTDOWN_REGISTRATION phsr)
{
    _asm {
        // Find the local APIC
        xor edx, edx
        xor eax, eax
        mov ecx, 0x1b
        rdmsr

        // Do nothing if already disabled
        test ax, 0x800
        jz disabled

        // Disable it -- shouldn't need to mark as s/w disabled
        and ah, 0xf7
        wrmsr
disabled:
        ret 4
    }
}

/* And here are some functions just to replace missing kernel functions */
#undef PsGetCurrentThreadId
HANDLE PsGetCurrentThreadId( VOID )
{
    return PsGetCurrentThread()->UniqueThread;
}
