/*++ Copyright (c) 2001 Microsoft Corporation Module Name: TrackFS.cpp Abstract: Track the directories the app looks at and record them into a file. History: 04/04/2001 maonis Created --*/ #include "precomp.h" #include "utils.h" #include "secutils.h" #include #define APPPATCH_DIR L"AppPatch\\" #define APPPATCH_DIR_LEN (sizeof(APPPATCH_DIR) / sizeof(WCHAR) - 1) #define TRACK_LOG_SUFFIX L".LUA.log" #define TRACK_LOG_SUFFIX_LEN (sizeof(TRACK_LOG_SUFFIX) / sizeof(WCHAR) - 1) struct DISTINCT_OBJ { DISTINCT_OBJ* next; LPWSTR pwszName; }; static WCHAR g_wszProgramFiles[MAX_PATH] = L""; static DWORD g_cProgramFiles = 0; DWORD LuatpGetProgramFilesDirW() { if (g_cProgramFiles == 0) { WCHAR wszProgramFiles[MAX_PATH]; if (GetEnvironmentVariableW(L"ProgramFiles", wszProgramFiles, MAX_PATH)) { DWORD dwSize = GetLongPathNameW(wszProgramFiles, g_wszProgramFiles, MAX_PATH); if (dwSize <= MAX_PATH) { // // Only if we successfully got the path and it's not more // than MAX_PATH will we set the global values. // g_cProgramFiles = dwSize; } else { g_wszProgramFiles[0] = L'\0'; } } } return g_cProgramFiles; } BOOL LuatpIsProgramFilesDirectory( LPCWSTR pwszPath ) { LuatpGetProgramFilesDirW(); if (g_cProgramFiles) { return !_wcsnicmp(pwszPath, g_wszProgramFiles, g_cProgramFiles); } return FALSE; } // We only record things when the file // 1) is not in the user profile dir - in which we know we don't need to redirect; // 2) is not in the program files dir - in which we know we will need to redirect; // because in those cases we know what to do so the user doesn't need to make the // choice. BOOL LuatpShouldRecord( LPCWSTR pwszPath ) { //if (LuatpIsUserDirectory(pwszPath) || // LuatpIsProgramFilesDirectory(pwszPath)) if (IsUserDirectory(pwszPath)) { return FALSE; } return TRUE; } LPWSTR LuatpGetLongObjectName( LPCWSTR pwszName, BOOL fIsDirectory ) { BOOL fIsSuccess = FALSE; LPWSTR pwszLongNameObject = NULL; LPWSTR pwszObject = NULL; if (!pwszName) { return NULL; } // // First get the full path. // DWORD cFullPath = GetFullPathNameW(pwszName, 0, NULL, NULL); if (!cFullPath) { DPF("TrackFS", eDbgLevelError, "[LuatpGetLongObjectName] Failed to get the length of full path: %d", GetLastError()); return NULL; } // // We allocate one more char to make space for the trailing slash for dir names. // pwszObject = new WCHAR [cFullPath + 1]; if (!pwszObject) { DPF("TrackFS", eDbgLevelError, "[LuatpGetLongObjectName] Failed to allocate %d WCHARs", cFullPath + 1); return NULL; } if (!GetFullPathNameW(pwszName, cFullPath, pwszObject, NULL)) { DPF("TrackFS", eDbgLevelError, "[LuatpGetLongObjectName] Failed to get the full path: %d", GetLastError()); goto EXIT; } // // If it's not a valid file name, we don't add it. // if (wcslen(pwszObject) < 2 || !iswalpha(pwszObject[0]) || (pwszObject[1] != L':')) { goto EXIT; } if (fIsDirectory) { // // If it's a directory we make sure there's a trailing slash. // if (pwszObject[cFullPath - 2] != L'\\') { pwszObject[cFullPath - 1] = L'\\'; pwszObject[cFullPath] = L'\0'; } } // // Convert it to all lower case. // _wcslwr(pwszObject); // // Convert it to the long names. // DWORD cLongPath = GetLongPathName(pwszObject, NULL, 0); if (!cLongPath) { DPF("TrackFS", eDbgLevelError, "[LuatpGetLongObjectName] Failed to get the length of long path: %d", GetLastError()); goto EXIT; } pwszLongNameObject = new WCHAR [cLongPath]; if (!pwszLongNameObject) { DPF("TrackFS", eDbgLevelError, "[LuatpGetLongObjectName] Failed to allocate %d WCHARs", cLongPath); goto EXIT; } if (!GetLongPathName(pwszObject, pwszLongNameObject, cLongPath)) { DPF("TrackFS", eDbgLevelError, "[LuatpGetLongObjectName] Failed to get the long path: %d", GetLastError()); goto EXIT; } if (LuatpShouldRecord(pwszLongNameObject)) { // // We only record the objects that are not in the user profile directory. // fIsSuccess = TRUE; } EXIT: delete [] pwszObject; if (!fIsSuccess) { delete [] pwszLongNameObject; pwszLongNameObject = NULL; } return pwszLongNameObject; } /*++ The tracking class for the file system. History: 04/04/2001 maonis Created --*/ class CTrackObject { public: BOOL Init(); VOID Free(); // If the object name needs to be processed, eg, it's not the full path // or not the long name, call this method to process it first before // adding to the list. VOID AddObject(LPCWSTR pwszName, BOOL fIsDirectory); // If the caller already processed the file name, call this method // to add it directly. VOID AddObjectDirect(LPWSTR pwszName, BOOL fIsDirectory); // This is specially for GetTempFileName - we add // *.tmp after the path. VOID AddObjectGetTempFileName(LPCWSTR pwszPath); VOID Record(); private: BOOL AddObjectToList(LPWSTR pwszName, BOOL fIsDirectory); VOID WriteToLog(LPCWSTR pwszDir); HANDLE m_hLog; WCHAR m_wszLog[MAX_PATH]; DISTINCT_OBJ* m_pDistinctDirs; DISTINCT_OBJ* m_pDistinctFiles; DWORD m_cDistinctDirs; DWORD m_cDistinctFiles; }; BOOL CTrackObject::AddObjectToList( LPWSTR pwszName, BOOL fIsDirectory ) { BOOL fIsSuccess = FALSE; DISTINCT_OBJ* pDistinctObjs = fIsDirectory ? m_pDistinctDirs : m_pDistinctFiles; DISTINCT_OBJ* pObj = pDistinctObjs; // Check to see if this file already exists in the list. while (pObj) { if (!wcscmp(pObj->pwszName, pwszName)) { break; } pObj = pObj->next; } if (!pObj) { pObj = new DISTINCT_OBJ; if (pObj) { DWORD cLen = wcslen(pwszName); pObj->pwszName = new WCHAR [cLen + 1]; if (pObj->pwszName) { wcscpy(pObj->pwszName, pwszName); pObj->next = pDistinctObjs; if (fIsDirectory) { ++m_cDistinctDirs; m_pDistinctDirs = pObj; } else { ++m_cDistinctFiles; m_pDistinctFiles = pObj; } fIsSuccess = TRUE; } else { DPF("TrackFS", eDbgLevelError, "[CTrackObject::AddObjectToList] Error allocating %d WCHARs", cLen + 1); } } else { DPF("TrackFS", eDbgLevelError, "[CTrackObject::AddObjectToList] Error allocating memory for new node"); } } return fIsSuccess; } /*++ Function Description: For files it's simple - we just store the file name in a list and search through the list to see if it's already in the list. If it is we are done; else we add it to the beginning of the list. We don't expect there are too many calls to modify files so a linked list is fine. Arguments: IN pwszFileName - file name. Return Value: none. History: 05/08/2001 maonis Created --*/ VOID CTrackObject::AddObject( LPCWSTR pwszName, BOOL fIsDirectory ) { BOOL fIsSuccess = FALSE; LPWSTR pwszLongNameObject = LuatpGetLongObjectName(pwszName, fIsDirectory); if (pwszLongNameObject) { AddObjectToList(pwszLongNameObject, fIsDirectory); delete [] pwszLongNameObject; } } VOID CTrackObject::AddObjectDirect( LPWSTR pwszName, BOOL fIsDirectory ) { if (pwszName) { AddObjectToList(pwszName, fIsDirectory); } } /*++ Function Description: Write the directory to the log as ANSI characters. Note this method uses 2 str* routines and it IS DBCS aware. Arguments: IN pwszDir - the directory to write to the log. Return Value: None. History: 04/04/2001 maonis Created --*/ VOID CTrackObject::WriteToLog( LPCWSTR pwsz) { if (!pwsz || !*pwsz) { return; } // // Get the number of bytes required to convert the string to ansi. // DWORD dwSize = WideCharToMultiByte(CP_ACP, 0, pwsz, -1, NULL, 0, NULL, NULL); LPSTR psz = new CHAR [dwSize + 2]; if (psz) { WideCharToMultiByte(CP_ACP, 0, pwsz, -1, psz, dwSize, 0, 0); psz[dwSize - 1] = '\r'; psz[dwSize] = '\n'; psz[dwSize + 1] = '\0'; DWORD dwBytesWritten = 0; WriteFile( m_hLog, psz, dwSize + 1, &dwBytesWritten, NULL); delete [] psz; } else { DPF("TrackFS", eDbgLevelError, "[CTrackObject::WriteToLog] Failed to allocate %d CHARs", dwSize); } } /*++ Function Description: Create the log file in %windir%\apppatch directory. We want to make sure we can create this file so we don't run the app to the end only to find that we can't record the results into a file. Arguments: None Return Value: TRUE - if log created successfully. FALSE otherwise. History: 04/04/2001 maonis Created --*/ BOOL CTrackObject::Init() { m_pDistinctDirs = NULL; m_pDistinctFiles = NULL; m_cDistinctDirs = 0; m_cDistinctFiles = 0; WCHAR szModuleName[MAX_PATH + 1] = L""; LPWSTR pwszModuleNameStart = NULL; LPWSTR pwszModuleNameExtStart = NULL; DWORD cAppPatchLen = 0; DWORD cModuleNameLen = 0; DWORD cTotalLen = 0; DWORD dwRes = 0; GetSystemRootDirW(); // // GetModuleFileNameW is an awful API. If you don't pass in a buffer // that's big enough to hold the module (including the terminating NULL), it // returns the passed in buffer size (NOT the required length) which means // it doesn't return an error - it just fills upto the passed in buffer size // so does NOT NULL terminate the string. So we set the last char to NULL and // make sure it doesn't get overwritten. // szModuleName[MAX_PATH] = L'\0'; dwRes = GetModuleFileNameW(NULL, szModuleName, MAX_PATH + 1); if (!dwRes || szModuleName[MAX_PATH] != L'\0') { DPF("TrackFS", eDbgLevelError, "[CTrackObject::Init] Error getting the module name: %d", GetLastError()); return FALSE; } pwszModuleNameStart = wcsrchr(szModuleName, L'\\'); if (!pwszModuleNameStart) { DPF("TrackFS", eDbgLevelError, "[CTrackObject::Init] We can't find where the file name starts??? %S", szModuleName); return FALSE; } ++pwszModuleNameStart; cModuleNameLen = wcslen(pwszModuleNameStart); // // We don't need the path anymore. // memmove(szModuleName, pwszModuleNameStart, cModuleNameLen * sizeof(WCHAR)); szModuleName[cModuleNameLen] = L'\0'; // // Get rid of the extension. // pwszModuleNameExtStart = wcsrchr(szModuleName, L'.'); // // If there's no extension we just use the whole file name. // if (pwszModuleNameExtStart) { *pwszModuleNameExtStart = L'\0'; } cModuleNameLen = wcslen(szModuleName); // // Make sure we don't have a buffer overflow. // cTotalLen = g_cSystemRoot + APPPATCH_DIR_LEN + // %windir%\AppPatch\ dir cModuleNameLen + // module name without extension TRACK_LOG_SUFFIX_LEN + // .LUA.log suffix 1; // terminating NULL if (cTotalLen > MAX_PATH) { DPF("TrackFS", eDbgLevelError, "[CTrackObject::Init] The file name is %d chars - " "we don't handle names longer than MAX_PATH", cTotalLen); return FALSE; } // // Construct the file name. // wcsncpy(m_wszLog, g_wszSystemRoot, g_cSystemRoot); wcsncpy(m_wszLog + g_cSystemRoot, APPPATCH_DIR, APPPATCH_DIR_LEN); wcsncpy(m_wszLog + (g_cSystemRoot + APPPATCH_DIR_LEN), szModuleName, cModuleNameLen); wcsncpy(m_wszLog + (g_cSystemRoot + APPPATCH_DIR_LEN + cModuleNameLen), TRACK_LOG_SUFFIX, TRACK_LOG_SUFFIX_LEN); m_wszLog[cTotalLen - 1] = L'\0'; // // Delete the file first if it exists. // DeleteFileW(m_wszLog); if ((m_hLog = CreateFileW( m_wszLog, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL)) == INVALID_HANDLE_VALUE) { DPF("TrackFS", eDbgLevelError, "[CTrackObject::Init] Error creating log %S: %d", m_wszLog, GetLastError()); return FALSE; } else { DPF("TrackFS", eDbgLevelInfo, "[CTrackObject::Init] Created the log %S", m_wszLog); CloseHandle(m_hLog); return TRUE; } } /*++ Function Description: Free the linked list. Arguments: None Return Value: None History: 04/04/2001 maonis Created --*/ VOID CTrackObject::Free() { DISTINCT_OBJ* pDir = m_pDistinctDirs; DISTINCT_OBJ* pTempDir; while (pDir) { pTempDir = pDir; pDir = pDir->next; delete [] pTempDir->pwszName; delete pTempDir; } } /*++ Function Description: Write the list of directories to the log. Arguments: None Return Value: None History: 04/04/2001 maonis Created --*/ VOID CTrackObject::Record() { if ((m_hLog = CreateFileW( m_wszLog, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL)) != INVALID_HANDLE_VALUE) { // Empty the old log. SetFilePointer(m_hLog, 0, 0, FILE_BEGIN); SetEndOfFile(m_hLog); WCHAR wszHeader[32]; if (_snwprintf(wszHeader, 31, L"D%d", m_cDistinctDirs) < 0) { DPF("TrackFS", eDbgLevelError, "[CTrackObject::Record] Too many dirs??? %d", m_cDistinctDirs); return; } wszHeader[31] = L'\0'; WriteToLog(wszHeader); // // Dump the directories to the log - each dir is on its own line. // DISTINCT_OBJ* pDir = m_pDistinctDirs; while (pDir) { WriteToLog(pDir->pwszName); pDir = pDir->next; } if (_snwprintf(wszHeader, 31, L"F%d", m_cDistinctFiles) < 0) { DPF("TrackFS", eDbgLevelError, "[CTrackObject::Record] Too many files??? %d", m_cDistinctFiles); return; } wszHeader[31] = L'\0'; WriteToLog(wszHeader); // // Dump the files to the log - each file is on its own line. // DISTINCT_OBJ* pFile = m_pDistinctFiles; while (pFile) { WriteToLog(pFile->pwszName); pFile = pFile->next; } CloseHandle(m_hLog); } // Make the file hidden so people don't accidently mess it up. DWORD dwAttrib = GetFileAttributes(m_wszLog); SetFileAttributes(m_wszLog, dwAttrib | FILE_ATTRIBUTE_HIDDEN); } CTrackObject g_td; /*++ Custom exception handler. --*/ LONG ExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo ) { // Whenever we get an unhandled exception, we dump the stuff to the log. g_td.Record(); return EXCEPTION_CONTINUE_SEARCH; } // // Exported APIs. // HANDLE LuatCreateFileW( LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile ) { DPF("TrackFS", eDbgLevelInfo, "[CreateFileW] lpFileName=%S; dwDesiredAccess=0x%08x; dwCreationDisposition=%d", lpFileName, dwDesiredAccess, dwCreationDisposition); HANDLE hFile = CreateFileW( lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); if (hFile != INVALID_HANDLE_VALUE) { LUA_GET_API_ERROR; if (RequestWriteAccess(dwCreationDisposition, dwDesiredAccess)) { g_td.AddObject(lpFileName, FALSE); } LUA_SET_API_ERROR; } return hFile; } BOOL LuatCopyFileW( LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName, BOOL bFailIfExists ) { DPF("TrackFS", eDbgLevelInfo, "[CopyFileW] lpExistingFileName=%S; lpNewFileName=%S; bFailIfExists=%d", lpExistingFileName, lpNewFileName, bFailIfExists); BOOL bRet = CopyFileW(lpExistingFileName, lpNewFileName, bFailIfExists); if (bRet) { LUA_GET_API_ERROR; g_td.AddObject(lpNewFileName, FALSE); LUA_SET_API_ERROR; } return bRet; } BOOL LuatCreateDirectoryW( LPCWSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes ) { DPF("TrackFS", eDbgLevelInfo, "[CreateDirectoryW] lpPathName=%S", lpPathName); BOOL bRet = CreateDirectoryW(lpPathName, lpSecurityAttributes); if (bRet) { LUA_GET_API_ERROR; g_td.AddObject(lpPathName, TRUE); LUA_SET_API_ERROR; } return bRet; } BOOL LuatSetFileAttributesW( LPCWSTR lpFileName, DWORD dwFileAttributes ) { DPF("TrackFS", eDbgLevelInfo, "[SetFileAttributesW] lpFileName=%S", lpFileName); BOOL bRet = SetFileAttributesW(lpFileName, dwFileAttributes); if (bRet) { LUA_GET_API_ERROR; DWORD dwAttrib = GetFileAttributesW(lpFileName); if (dwAttrib != -1) { g_td.AddObject(lpFileName, dwAttrib & FILE_ATTRIBUTE_DIRECTORY); } LUA_SET_API_ERROR; } return bRet; } BOOL LuatDeleteFileW( LPCWSTR lpFileName ) { DPF("TrackFS", eDbgLevelInfo, "[DeleteFileW] lpFileName=%S", lpFileName); LPWSTR pwszTempFile = LuatpGetLongObjectName(lpFileName, FALSE); BOOL bRet = DeleteFileW(lpFileName); if (bRet) { LUA_GET_API_ERROR; g_td.AddObjectDirect(pwszTempFile, FALSE); LUA_SET_API_ERROR; } delete [] pwszTempFile; return bRet; } BOOL LuatMoveFileW( LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName ) { DPF("TrackFS", eDbgLevelInfo, "[MoveFileW] lpExistingFileName=%S; lpNewFileName=%S", lpExistingFileName, lpNewFileName); LPWSTR pwszTempFile = LuatpGetLongObjectName(lpExistingFileName, FALSE); BOOL bRet = MoveFileW(lpExistingFileName, lpNewFileName); if (bRet) { LUA_GET_API_ERROR; g_td.AddObjectDirect(pwszTempFile, FALSE); g_td.AddObject(lpNewFileName, FALSE); LUA_SET_API_ERROR; } delete [] pwszTempFile; return bRet; } BOOL LuatRemoveDirectoryW( LPCWSTR lpPathName ) { DPF("TrackFS", eDbgLevelInfo, "[RemoveDirectoryW] lpPathName=%S", lpPathName); LPWSTR pwszTempDir = LuatpGetLongObjectName(lpPathName, TRUE); BOOL bRet = RemoveDirectoryW(lpPathName); if (bRet) { LUA_GET_API_ERROR; g_td.AddObjectDirect(pwszTempDir, TRUE); LUA_SET_API_ERROR; } delete [] pwszTempDir; return bRet; } UINT LuatGetTempFileNameW( LPCWSTR lpPathName, LPCWSTR lpPrefixString, UINT uUnique, LPWSTR lpTempFileName ) { DPF("TrackFS", eDbgLevelInfo, "[GetTempFileNameW] lpPathName=%S", lpPathName); UINT uiRet = GetTempFileNameW(lpPathName, lpPrefixString, uUnique, lpTempFileName); if (uiRet && !uUnique) { LUA_GET_API_ERROR; g_td.AddObject(lpTempFileName, FALSE); LUA_SET_API_ERROR; } return uiRet; } BOOL LuatWritePrivateProfileStringW( LPCWSTR lpAppName, LPCWSTR lpKeyName, LPCWSTR lpString, LPCWSTR lpFileName ) { DPF("TrackFS", eDbgLevelInfo, "[WritePrivateProfileStringW] lpAppName=%S; lpKeyName=%S; lpString=%S; lpFileName=%S", lpAppName, lpKeyName, lpString, lpFileName); BOOL bRet = WritePrivateProfileStringW( lpAppName, lpKeyName, lpString, lpFileName); if (bRet) { LUA_GET_API_ERROR; WCHAR wszFileName[MAX_PATH] = L""; MakeFileNameForProfileAPIsW(lpFileName, wszFileName); g_td.AddObject(wszFileName, FALSE); LUA_SET_API_ERROR; } return bRet; } BOOL LuatWritePrivateProfileSectionW( LPCWSTR lpAppName, LPCWSTR lpString, LPCWSTR lpFileName ) { DPF("TrackFS", eDbgLevelInfo, "[WritePrivateProfileSectionW] lpAppName=%S; lpString=%S; lpFileName=%S", lpAppName, lpString, lpFileName); BOOL bRet = WritePrivateProfileSectionW( lpAppName, lpString, lpFileName); if (bRet) { LUA_GET_API_ERROR; WCHAR wszFileName[MAX_PATH] = L""; MakeFileNameForProfileAPIsW(lpFileName, wszFileName); g_td.AddObject(wszFileName, FALSE); LUA_SET_API_ERROR; } return bRet; } BOOL LuatWritePrivateProfileStructW( LPCWSTR lpszSection, LPCWSTR lpszKey, LPVOID lpStruct, UINT uSizeStruct, LPCWSTR szFile ) { DPF("TrackFS", eDbgLevelInfo, "[WritePrivateProfileStructW] lpszKey=%S; szFile=%S", lpszKey, szFile); BOOL bRet = WritePrivateProfileStructW( lpszSection, lpszKey, lpStruct, uSizeStruct, szFile); if (bRet) { LUA_GET_API_ERROR; WCHAR wszFileName[MAX_PATH] = L""; MakeFileNameForProfileAPIsW(szFile, wszFileName); g_td.AddObject(wszFileName, FALSE); LUA_SET_API_ERROR; } return bRet; } HFILE LuatOpenFile( LPCSTR lpFileName, LPOFSTRUCT lpReOpenBuff, UINT uStyle ) { DPF("TrackFS", eDbgLevelInfo, "[OpenFile] lpFileName=%s", lpFileName); STRINGA2W wstrFileName(lpFileName); LPWSTR pwszTempFile = LuatpGetLongObjectName(wstrFileName, FALSE); HFILE hFile = OpenFile(lpFileName, lpReOpenBuff, uStyle); if (hFile != HFILE_ERROR) { if (uStyle & OF_CREATE || uStyle & OF_DELETE || uStyle & OF_READWRITE || uStyle & OF_WRITE) { LUA_GET_API_ERROR; g_td.AddObjectDirect(pwszTempFile, FALSE); LUA_SET_API_ERROR; } } delete [] pwszTempFile; return hFile; } HFILE Luat_lopen( LPCSTR lpPathName, int iReadWrite ) { DPF("TrackFS", eDbgLevelInfo, "[_lopen] lpPathName=%s", lpPathName); HFILE hFile = _lopen(lpPathName, iReadWrite); if (hFile != HFILE_ERROR) { if (iReadWrite & OF_READWRITE || iReadWrite & OF_WRITE) { LUA_GET_API_ERROR; STRINGA2W wstrPathName(lpPathName); g_td.AddObject(wstrPathName, FALSE); LUA_SET_API_ERROR; } } return hFile; } HFILE Luat_lcreat( LPCSTR lpPathName, int iAttribute ) { DPF("TrackFS", eDbgLevelInfo, "[_lcreat] lpPathName=%s", lpPathName); HFILE hFile = _lcreat(lpPathName, iAttribute); if (hFile != HFILE_ERROR) { LUA_GET_API_ERROR; STRINGA2W wstrPathName(lpPathName); g_td.AddObject(wstrPathName, FALSE); LUA_SET_API_ERROR; } return hFile; } BOOL LuatFSInit() { SetUnhandledExceptionFilter(ExceptionFilter); return g_td.Init(); } VOID LuatFSCleanup() { g_td.Record(); g_td.Free(); }