#include "fsUtils.h" #include "driveManager.h" #include /* ============================================================================ FsUtil - OG Xbox / XDK utility functions used by the file browser. - Drive letter mapping (IoCreateSymbolicLink) - Directory listing and basic FS ops - Copy/move helpers with progress callbacks - .xbe launcher (remaps D: and calls XLaunchNewImageA) - FATX cache format helpers (X/Y/Z) via XapiFormatFATVolumeEx - Integrated DVD helpers (tray state, media sniff, cold remount) - No dependency on undocumented.h; minimal kernel shims are declared here. ============================================================================ */ // TRAY_* and DRIVE_* come from FsUtil.h (kept out of this .cpp on purpose). #ifndef FILE_READ_ONLY_VOLUME #define FILE_READ_ONLY_VOLUME 0x00080000u // for GetVolumeInformationA #endif // ----- Small STRING helpers -------------------------------------------------- // Build an XDK STRING directly (avoid Rtl* to keep header surface tiny) static inline void BuildString(STRING& s, const char* z){ USHORT L=(USHORT)strlen(z); s.Length=L; s.MaximumLength=L+1; s.Buffer=(PCHAR)z; } // "\??\X:" is the DOS devices directory; drive letters live here static inline void MakeDosString(char* out, size_t cap, const char* letter){ _snprintf(out, (int)cap, "\\??\\%s", letter); out[cap-1]=0; } // ============================================================================ // Copy progress callback plumbing // ============================================================================ CopyProgressFn CopyProgress::g_copyProgFn = 0; void* CopyProgress::g_copyProgUser = 0; void SetCopyProgressCallback(CopyProgressFn fn, void* user){ CopyProgress::g_copyProgFn = fn; CopyProgress::g_copyProgUser = user; } // ============================================================================ // Attribute & volume helpers // ============================================================================ // Remove READONLY/SYSTEM/HIDDEN so we can delete/overwrite stubborn files. static inline void StripROSysHiddenA(const char* path){ DWORD a = GetFileAttributesA(path); if (a == INVALID_FILE_ATTRIBUTES) return; DWORD na = a & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN); if (na != a) SetFileAttributesA(path, na); } // Minimal wrapper; keeps SMC calls private to this TU. class CIoSupport { public: CIoSupport(){ m_dwTrayState=0; m_dwTrayCount=0; m_dwLastTrayState=0; } DWORD GetTrayState(){ HalReadSMCTrayState(&m_dwTrayState, &m_dwTrayCount); // returns TRAY_* m_dwLastTrayState = m_dwTrayState; return m_dwTrayState; } // Unused at the moment but intentionally kept as handy hooks: HRESULT EjectTray(){ HalWriteSMBusValue(0x20, 0x0C, FALSE, 0x00); return S_OK; } HRESULT CloseTray(){ HalWriteSMBusValue(0x20, 0x0C, FALSE, 0x01); return S_OK; } private: DWORD m_dwTrayState, m_dwTrayCount, m_dwLastTrayState; }; // DVD size cache (expensive to recompute on CDFS; keyed by volume serial) static DWORD g_dvdSerialCache = 0xFFFFFFFFu; // 0xFFFFFFFF => unknown static ULONGLONG g_dvdUsedCache = 0; // bytes used on the disc static ULONGLONG g_dvdTotalCache = 0; // disc capacity (for reference) static void DvdInvalidateSizeCache(){ g_dvdSerialCache = 0xFFFFFFFFu; g_dvdUsedCache = 0; g_dvdTotalCache = 0; } // Is D:\ convenience (app also uses a local inline; this is exported) bool IsDPath(const char* p) { return (_strnicmp(p, "DVD-ROM", 7) == 0) ? true : false; } // Volume serial helper (used for cache key) bool GetDvdVolumeSerial(DWORD* outSerial) { if (!outSerial) return false; if (GetFileAttributesA("DVD-ROM:\\") == INVALID_FILE_ATTRIBUTES) return false; DWORD serial = 0; if (!GetVolumeInformationA("DVD-ROM:\\", NULL, 0, &serial, NULL, NULL, NULL, 0)) return false; *outSerial = serial; return true; } // Simple media sniff: 1=game, 2=video, 3=data, 0=unknown. Writes label. int DvdDetectMediaSimple(char* outLabel, size_t cap){ if (!outLabel || cap==0) return 0; outLabel[0]=0; // Xbox game? DWORD a = GetFileAttributesA("DVD-ROM:\\default.xbe"); if (a != INVALID_FILE_ATTRIBUTES && !(a & FILE_ATTRIBUTE_DIRECTORY)){ _snprintf(outLabel, (int)cap, "\x9C Game"); outLabel[cap - 1] = 0; return 1; } // DVD-Video? a = GetFileAttributesA("DVD-ROM:\\VIDEO_TS"); if (a != INVALID_FILE_ATTRIBUTES && (a & FILE_ATTRIBUTE_DIRECTORY)){ _snprintf(outLabel, (int)cap, "\x9C Video"); outLabel[cap-1]=0; return 2; } // Any content at all => Data WIN32_FIND_DATAA fd; HANDLE h = FindFirstFileA("DVD-ROM:\\*", &fd); if (h != INVALID_HANDLE_VALUE){ do{ const char* n = fd.cFileName; if (!strcmp(n,".") || !strcmp(n,"..")) continue; _snprintf(outLabel, (int)cap, "\x9C Data"); outLabel[cap-1]=0; FindClose(h); return 3; }while(FindNextFileA(h, &fd)); FindClose(h); } _snprintf(outLabel, (int)cap, "\x9C Unknown"); outLabel[cap-1]=0; return 0; } // Return a TRAY_* code only when the tray/media state *changes*; otherwise READY. DWORD DvdGetDriveStateOneShot() { static DWORD s_lastCode = 0xFFFFFFFFu; static DWORD s_lastSerial; static CIoSupport s_io; DWORD curCode = s_io.GetTrayState(); if (s_lastCode != curCode) { s_lastCode = curCode; return curCode; } if (curCode == TRAY_CLOSED_MEDIA_PRESENT) { DWORD curSerial = 0; if (GetDvdVolumeSerial(&curSerial) && s_lastSerial != curSerial) { s_lastSerial = curSerial; return curCode; } } return TRAY_NO_CHANGE; } // ============================================================================ // Drive discovery for the drive list // ============================================================================ namespace { // We only care about the OG Xbox set of letters int g_presentIdx[16]; int g_presentCount = 0; inline int ci_cmp(const char* a,const char* b){ return _stricmp(a,b); } } // Build drive items (e.g., "E:\") into 'out'. void BuildDriveItems(std::vector& out) { out.clear(); // Ask driveManager for mounted drives pointerVector* drives = driveManager::getMountedDrives(); if (!drives) return; for (uint32_t i = 0; i < drives->count(); ++i) { const char* mount = drives->get(i); if (!mount || !mount[0]) continue; Item it; ZeroMemory(&it, sizeof(it)); // Build root path: "HDD0-C:\" _snprintf(it.name, sizeof(it.name), "%s:\\", mount); it.name[sizeof(it.name) - 1] = 0; it.isDir = true; it.size = 0; it.isUpEntry = false; it.marked = false; // Icon selection (example logic) if (!_strnicmp(mount, "DVD-ROM", 7)) it.icon = '\x9C'; // DVD else if (!_strnicmp(mount, "HDD0", 4)) it.icon = '\x9A'; // HDD0 (primary disk) else if (!_strnicmp(mount, "HDD1", 4)) it.icon = '\x9B'; // HDD1 (secondary disk) else if (!_strnicmp(mount, "H", 1) || !_strnicmp(mount, "I", 1) || !_strnicmp(mount, "J", 1) || !_strnicmp(mount, "K", 1) || !_strnicmp(mount, "L", 1) || !_strnicmp(mount, "M", 1) || !_strnicmp(mount, "N", 1) || !_strnicmp(mount, "O", 1)) it.icon = '\x98'; else it.icon = '\x9D'; // default to blank out.push_back(it); } delete drives; // pointerVector allocated it } // ============================================================================ // Path helpers // ============================================================================ void EnsureTrailingSlash(char* s,size_t cap){ size_t n = strlen(s); if(n && s[n-1] != '\\' && n + 1 < cap) { s[n] = '\\'; s[n+1] = 0; } } // JoinPath does not normalize components; input must be well-formed. void JoinPath(char* dst, size_t cap, const char* base, const char* name){ size_t bl = strlen(base); if(bl && base[bl-1] == '\\') _snprintf(dst, (int)cap, "%s%s", base, name); else _snprintf(dst, (int)cap, "%s\\%s", base, name); dst[cap-1] = 0; } bool IsDriveRoot(const char* path) { if (!path || !path[0]) return false; size_t len = strlen(path); // Must end with ":\" if (len < 3) return false; if (path[len - 1] != '\\') return false; // Find colon const char* colon = strchr(path, ':'); if (!colon) return false; // Colon must be immediately before the slash if (colon[1] != '\\') return false; // Colon must be the *only* colon if (strchr(colon + 1, ':') != nullptr) return false; return true; } void ParentPath(char* path) { if (!path) return; // Already at drive list if (path[0] == 0) return; // If we're at a mounted drive root go back to drive list if (IsDriveRoot(path)) { path[0] = 0; return; } // Strip trailing slashes size_t n = strlen(path); while (n && path[n - 1] == '\\') path[--n] = 0; // Remove last path component char* p = strrchr(path, '\\'); if (!p) { path[0] = 0; return; } *(p + 1) = 0; } void NormalizeDirA(char* s){ // "E:" -> "E:\" ; always ensure trailing slash size_t n = strlen(s); // Needs rewrite here if (n==2 && s[1]==':'){ s[2]='\\'; s[3]=0; return; } EnsureTrailingSlash(s, 512); } // Sort: directories first, then case-insensitive by name. static bool ItemLess(const Item& a,const Item& b){ if(a.isDir!=b.isDir) return a.isDir>b.isDir; return ci_cmp(a.name,b.name)<0; } // ============================================================================ // Directory listing // - Prepends a synthetic ".." entry for non-root folders. // - Sorts (dirs first, then by name) while keeping the ".." at index 0. // ============================================================================ bool ListDirectory(const char* path, std::vector& out) { out.clear(); // For non-root, push "..\" to allow going up. if (strlen(path) > 3) { Item up; ZeroMemory(&up, sizeof(up)); strncpy(up.name, "..\\", 3); up.isDir = true; up.size = 0; up.isUpEntry = true; up.marked = false; up.icon = '\x9D'; out.push_back(up); } char base[512]; _snprintf(base,sizeof(base), "%s", path); base[sizeof(base) - 1] = 0; EnsureTrailingSlash(base, sizeof(base)); char mask[512]; _snprintf(mask, sizeof(mask), "%s*", base); mask[sizeof(mask) - 1] = 0; WIN32_FIND_DATAA fd; ZeroMemory(&fd, sizeof(fd)); HANDLE h = FindFirstFileA(mask, &fd); if (h == INVALID_HANDLE_VALUE) return false; do { const char* n = fd.cFileName; if (!strcmp(n, ".") || !strcmp(n, "..")) continue; Item it; ZeroMemory(&it, sizeof(it)); strncpy(it.name, n, 255); it.name[255] = 0; bool isZip = false; if (strlen(n) >= 4 && _memicmp(".zip", n + strlen(n) - 4, 4) == 0) isZip = true; bool isXbe = false; if (strlen(n) >= 4 && _memicmp(".xbe", n + strlen(n) - 4, 4) == 0) isXbe = true; it.isDir = (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; it.size = (((ULONGLONG)fd.nFileSizeHigh) << 32) | fd.nFileSizeLow; it.isUpEntry = false; it.marked = false; it.icon = (it.isDir) ? '\x9F' : (isZip) ? '\x99' : (isXbe) ? '\x95' : '\x9E'; out.push_back(it); } while (FindNextFileA(h, &fd)); FindClose(h); size_t start = (strlen(path) > 3) ? 1 : 0; // keep ".." in place if (out.size() > start + 1) std::sort(out.begin() + start, out.end(), ItemLess); return true; } // ============================================================================ // Misc info helpers // ============================================================================ void FormatSize(ULONGLONG bytes, char* out, size_t cap) { if (!out || cap == 0) return; out[0] = 0; const ULONGLONG KB = 1024ULL; const ULONGLONG MB = KB * 1024ULL; const ULONGLONG GB = MB * 1024ULL; const ULONGLONG TB = GB * 1024ULL; double val = 0.0; const char* unit = "B"; if (bytes >= TB) { val = (double)bytes / (double)TB; unit = "TB"; } else if (bytes >= GB) { val = (double)bytes / (double)GB; unit = "GB"; } else if (bytes >= MB) { val = (double)bytes / (double)MB; unit = "MB"; } else if (bytes >= KB) { val = (double)bytes / (double)KB; unit = "KB"; } else { _snprintf(out, (int)cap, "%llu B", (unsigned long long)bytes); out[cap-1] = 0; return; } // Two decimals keeps the footer stable but not too noisy (e.g., 3.09 GB) _snprintf(out, (int)cap, "%.2f %s", val, unit); out[cap-1] = 0; } // For normal drives, we return true "free / total". // For D:, CDFS reports "free=0". To match the UI label "Free / Total" and avoid // confusion, we intentionally return "0 / " for DVDs. // We recompute only when the volume serial changes. void GetDriveFreeTotal(const char* anyPathInDrive, ULONGLONG& freeBytes, ULONGLONG& totalBytes) { freeBytes = 0; totalBytes = 0; if (!anyPathInDrive || !anyPathInDrive[0]) return; // Extract mount root (up to and including ":\") char root[256]; root[0] = 0; const char* colon = strchr(anyPathInDrive, ':'); if (!colon || colon[1] != '\\') return; // not a valid mounted path size_t len = (colon - anyPathInDrive) + 2; // include ":\" if (len >= sizeof(root)) return; memcpy(root, anyPathInDrive, len); root[len] = 0; ULARGE_INTEGER avail, total, free; avail.QuadPart = total.QuadPart = free.QuadPart = 0; if (GetDiskFreeSpaceExA(root, &avail, &total, &free)) { freeBytes = free.QuadPart; totalBytes = total.QuadPart; } } // ============================================================================ // Basic FS ops // ============================================================================ bool DirExistsA(const char* path) { DWORD a = GetFileAttributesA(path); return (a != INVALID_FILE_ATTRIBUTES) && (a & FILE_ATTRIBUTE_DIRECTORY); } bool EnsureDirA(const char* path) { DWORD a = GetFileAttributesA(path); if (a != INVALID_FILE_ATTRIBUTES && (a & FILE_ATTRIBUTE_DIRECTORY)) return true; return CreateDirectoryA(path, NULL) ? true : false; } // Recursively delete with safety rails: // - Refuses drive roots // - Refuses read-only volumes (CDFS / cache) // - Clears READONLY/SYSTEM/HIDDEN before delete // - Continues on child failures; tiny retry on RemoveDirectoryA bool DeleteRecursiveA(const char* path) { if (!path || !path[0]) { SetLastError(ERROR_INVALID_PARAMETER); return false; } if (IsDriveRoot(path)) { SetLastError(ERROR_ACCESS_DENIED); return false; } DWORD a = GetFileAttributesA(path); if (a == INVALID_FILE_ATTRIBUTES) { SetLastError(ERROR_FILE_NOT_FOUND); return false; } // Make the target itself writable so final delete can succeed. StripROSysHiddenA(path); if (a & FILE_ATTRIBUTE_DIRECTORY) { // Enumerate children char mask[512]; JoinPath(mask, sizeof(mask), path, "*"); WIN32_FIND_DATAA fd; HANDLE h = FindFirstFileA(mask, &fd); if (h != INVALID_HANDLE_VALUE) { do { if (!strcmp(fd.cFileName,".") || !strcmp(fd.cFileName,"..")) continue; char sub[512]; JoinPath(sub, sizeof(sub), path, fd.cFileName); // Clear attributes on each child before deleting StripROSysHiddenA(sub); // Best-effort delete; continue on failure if (!DeleteRecursiveA(sub)){ // Optionally capture first error here } } while (FindNextFileA(h, &fd)); FindClose(h); } // Try removing the (now empty) directory (with a tiny retry) if (!RemoveDirectoryA(path)) { Sleep(1); StripROSysHiddenA(path); return RemoveDirectoryA(path) ? true : false; } return true; } else { // File: clear attributes then delete (retry once) if (!DeleteFileA(path)) { StripROSysHiddenA(path); return DeleteFileA(path) ? true : false; } return true; } } // ============================================================================ // File type helpers // ============================================================================ const char* GetExtension(const char* name){ if (!name) return NULL; char* ext = NULL; const char* delim = strrchr(name, '.'); if (delim && *(delim + 1)) { return (delim + 1); } return NULL; } bool HasXbeExt(const char* name){ if (!name) return false; const char* ext = GetExtension(name); return (ext && _stricmp(ext, "xbe") == 0); } // ============================================================================ // Copy (chunked) + recursive copy with progress/cancel // - 64 KiB fixed buffer (predictable RAM use on Xbox) // - Normalizes dest attributes before overwrite // - Progress callback may cancel; on cancel we delete the partial output // ============================================================================ static bool CopyFileChunkedA(const char* s, const char* d, ULONGLONG& inoutBytesDone, ULONGLONG totalBytes) { // Open source (read-only, allow readers to share) HANDLE hs = CreateFileA(s, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hs == INVALID_HANDLE_VALUE) return false; // Preflight dest: directory collision -> error; else clear R/O etc. DWORD da = GetFileAttributesA(d); if (da != INVALID_FILE_ATTRIBUTES) { if (da & FILE_ATTRIBUTE_DIRECTORY) { CloseHandle(hs); SetLastError(ERROR_ALREADY_EXISTS); return false; } DWORD na = da & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN); if (na != da) SetFileAttributesA(d, na); } // Create/overwrite dest (no sharing) HANDLE hd = CreateFileA(d, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hd == INVALID_HANDLE_VALUE){ CloseHandle(hs); return false; } const DWORD BUFSZ = 64 * 1024; char* buf = (char*)LocalAlloc(LMEM_FIXED, BUFSZ); if (!buf){ CloseHandle(hs); CloseHandle(hd); return false; } bool ok = true; for (;;){ DWORD rd = 0; if (!ReadFile(hs, buf, BUFSZ, &rd, NULL)) { ok = false; break; } if (rd == 0) break; DWORD wr = 0; if (!WriteFile(hd, buf, rd, &wr, NULL)) { ok = false; break; } inoutBytesDone += wr; // Progress/cancel callback if (CopyProgress::g_copyProgFn){ if (!CopyProgress::g_copyProgFn(inoutBytesDone, totalBytes, s, CopyProgress::g_copyProgUser)){ ok = false; break; // canceled } } } LocalFree(buf); CloseHandle(hs); CloseHandle(hd); // Normalize dest; on failure, remove partial if (!ok) { DeleteFileA(d); } else { SetFileAttributesA(d, FILE_ATTRIBUTE_NORMAL); } return ok; } // Core recursive copy: directory creation + per-file copy. static bool CopyRecursiveCoreA(const char* srcPath, const char* dstDir, ULONGLONG& inoutBytesDone, ULONGLONG totalBytes) { DWORD a = GetFileAttributesA(srcPath); if (a == INVALID_FILE_ATTRIBUTES) return false; const char* base = strrchr(srcPath, '\\'); base = base ? base+1 : srcPath; char dstPath[512]; JoinPath(dstPath, sizeof(dstPath), dstDir, base); if (a & FILE_ATTRIBUTE_DIRECTORY){ if (!EnsureDirA(dstPath)) return false; // Do NOT preserve source dir attributes SetFileAttributesA(dstPath, FILE_ATTRIBUTE_NORMAL); char mask[512]; JoinPath(mask, sizeof(mask), srcPath, "*"); WIN32_FIND_DATAA fd; HANDLE h = FindFirstFileA(mask, &fd); if (h != INVALID_HANDLE_VALUE){ do{ if (!strcmp(fd.cFileName,".") || !strcmp(fd.cFileName,"..")) continue; char subSrc[512]; JoinPath(subSrc, sizeof(subSrc), srcPath, fd.cFileName); if (!CopyRecursiveCoreA(subSrc, dstPath, inoutBytesDone, totalBytes)) { FindClose(h); return false; } }while (FindNextFileA(h,&fd)); FindClose(h); } return true; } else { return CopyFileChunkedA(srcPath, dstPath, inoutBytesDone, totalBytes); } } // Helpers to detect "copy into own subfolder" (case-insensitive). static void NormalizeSlashEnd(char* s, size_t cap) { size_t n = strlen(s); if (n && s[n-1] != '\\' && n+1 < cap) { s[n] = '\\'; s[n+1] = 0; } } static bool IsSubPathCI(const char* parent, const char* child) { char p[512], c[512]; _snprintf(p, sizeof(p), "%s", parent); p[sizeof(p)-1]=0; NormalizeSlashEnd(p, sizeof(p)); _snprintf(c, sizeof(c), "%s", child ); c[sizeof(c)-1]=0; NormalizeSlashEnd(c, sizeof(c)); return _strnicmp(p, c, strlen(p)) == 0; } // Public entry for recursive copy with progress and a safety check. bool CopyRecursiveWithProgressA(const char* srcPath, const char* dstDir, ULONGLONG totalBytes) { // Compute dstTop = dstDir\basename(srcPath) const char* base = strrchr(srcPath, '\\'); base = base ? base+1 : srcPath; char dstTop[512]; JoinPath(dstTop, sizeof(dstTop), dstDir, base); // Guard: prevent copying into own subfolder if (IsSubPathCI(srcPath, dstTop)) { SetLastError(ERROR_INVALID_PARAMETER); return false; } // Optional free-space preflight (skip if unknown) if (totalBytes > 0) { ULONGLONG freeB=0, totalB=0; GetDriveFreeTotal(dstDir, freeB, totalB); if (freeB > 0 && freeB < totalBytes) { SetLastError(ERROR_DISK_FULL); return false; } } ULONGLONG done = 0; return CopyRecursiveCoreA(srcPath, dstDir, done, totalBytes); } // ============================================================================ // Size calculation (recursive) // ============================================================================ ULONGLONG DirSizeRecursiveA(const char* path){ ULONGLONG sum = 0; DWORD a = GetFileAttributesA(path); if (a == INVALID_FILE_ATTRIBUTES) return 0; if (a & FILE_ATTRIBUTE_DIRECTORY){ char mask[512]; JoinPath(mask, sizeof(mask), path, "*"); WIN32_FIND_DATAA fd; HANDLE h = FindFirstFileA(mask, &fd); if (h != INVALID_HANDLE_VALUE){ do{ if (!strcmp(fd.cFileName,".") || !strcmp(fd.cFileName,"..")) continue; if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){ char sub[512]; JoinPath(sub, sizeof(sub), path, fd.cFileName); sum += DirSizeRecursiveA(sub); } else { sum += (((ULONGLONG)fd.nFileSizeHigh)<<32) | fd.nFileSizeLow; } } while (FindNextFileA(h, &fd)); FindClose(h); } } else { WIN32_FILE_ATTRIBUTE_DATA fad; if (GetFileAttributesExA(path, GetFileExInfoStandard, &fad)){ sum += (((ULONGLONG)fad.nFileSizeHigh)<<32) | fad.nFileSizeLow; } } return sum; } // ============================================================================ // Quick writability probe // ============================================================================ bool CanWriteHereA(const char* dir){ char test[512]; JoinPath(test, sizeof(test), dir, ".__xwtest$__"); test[sizeof(test)-1]=0; HANDLE h = CreateFileA(test, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL); if (h == INVALID_HANDLE_VALUE) return false; CloseHandle(h); DeleteFileA(test); return true; } // ============================================================================ // FATX-ish naming rules (close to dashboard behavior) // ============================================================================ bool IsBadFatxChar(char c){ if ((unsigned char)c < 32) return true; const char* bad = "\\/:*?\"<>|+,;=[]"; return (strchr(bad, c) != NULL); } void SanitizeFatxNameInPlace(char* s){ for (char* p=s; *p; ++p) if (IsBadFatxChar(*p)) *p = '_'; int n = (int)strlen(s); while (n>0 && (s[n-1]==' ' || s[n-1]=='.')) s[--n]=0; if (n > 42) { s[42]=0; n=42; } if (n==0 || (strcmp(s,".")==0) || (strcmp(s,"..")==0)) strcpy(s, "NewName"); } // ============================================================================ // .xbe launcher // - Accepts either a specific .xbe or a directory (implies "default.xbe"). // - Repoints D: to the folder's *device path* and calls XLaunchNewImageA. // ============================================================================ void GetDevicePathFromMountedPath(char* devPath, const char* mountPath) { if (!_memicmp(mountPath, "DVD-ROM", 7)) strcpy(devPath, "\\Device\\Cdrom0"); else if (!_memicmp(mountPath, "HDD0-C", 6)) strcpy(devPath, "\\Device\\Harddisk0\\Partition2"); else if (!_memicmp(mountPath, "HDD0-E", 6)) strcpy(devPath, "\\Device\\Harddisk0\\Partition1"); else if (!_memicmp(mountPath, "HDD0-F", 6)) strcpy(devPath, "\\Device\\Harddisk0\\Partition6"); else if (!_memicmp(mountPath, "HDD0-G", 6)) strcpy(devPath, "\\Device\\Harddisk0\\Partition7"); else if (!_memicmp(mountPath, "HDD0-H", 6)) strcpy(devPath, "\\Device\\Harddisk0\\Partition8"); else if (!_memicmp(mountPath, "HDD0-I", 6)) strcpy(devPath, "\\Device\\Harddisk0\\Partition9"); else if (!_memicmp(mountPath, "HDD0-J", 6)) strcpy(devPath, "\\Device\\Harddisk0\\Partition10"); else if (!_memicmp(mountPath, "HDD0-K", 6)) strcpy(devPath, "\\Device\\Harddisk0\\Partition11"); else if (!_memicmp(mountPath, "HDD0-L", 6)) strcpy(devPath, "\\Device\\Harddisk0\\Partition12"); else if (!_memicmp(mountPath, "HDD0-M", 6)) strcpy(devPath, "\\Device\\Harddisk0\\Partition13"); else if (!_memicmp(mountPath, "HDD0-N", 6)) strcpy(devPath, "\\Device\\Harddisk0\\Partition14"); else if (!_memicmp(mountPath, "HDD0-X", 6)) strcpy(devPath, "\\Device\\Harddisk0\\Partition3"); else if (!_memicmp(mountPath, "HDD0-Y", 6)) strcpy(devPath, "\\Device\\Harddisk0\\Partition4"); else if (!_memicmp(mountPath, "HDD0-Z", 6)) strcpy(devPath, "\\Device\\Harddisk0\\Partition5"); else if (!_memicmp(mountPath, "HDD0-C", 6)) strcpy(devPath, "\\Device\\Harddisk1\\Partition2"); else if (!_memicmp(mountPath, "HDD0-E", 6)) strcpy(devPath, "\\Device\\Harddisk1\\Partition1"); else if (!_memicmp(mountPath, "HDD0-F", 6)) strcpy(devPath, "\\Device\\Harddisk1\\Partition6"); else if (!_memicmp(mountPath, "HDD0-G", 6)) strcpy(devPath, "\\Device\\Harddisk1\\Partition7"); else if (!_memicmp(mountPath, "HDD0-H", 6)) strcpy(devPath, "\\Device\\Harddisk1\\Partition8"); else if (!_memicmp(mountPath, "HDD0-I", 6)) strcpy(devPath, "\\Device\\Harddisk1\\Partition9"); else if (!_memicmp(mountPath, "HDD0-J", 6)) strcpy(devPath, "\\Device\\Harddisk1\\Partition10"); else if (!_memicmp(mountPath, "HDD0-K", 6)) strcpy(devPath, "\\Device\\Harddisk1\\Partition11"); else if (!_memicmp(mountPath, "HDD0-L", 6)) strcpy(devPath, "\\Device\\Harddisk1\\Partition12"); else if (!_memicmp(mountPath, "HDD0-M", 6)) strcpy(devPath, "\\Device\\Harddisk1\\Partition13"); else if (!_memicmp(mountPath, "HDD0-N", 6)) strcpy(devPath, "\\Device\\Harddisk1\\Partition14"); else if (!_memicmp(mountPath, "HDD0-X", 6)) strcpy(devPath, "\\Device\\Harddisk1\\Partition3"); else if (!_memicmp(mountPath, "HDD0-Y", 6)) strcpy(devPath, "\\Device\\Harddisk1\\Partition4"); else if (!_memicmp(mountPath, "HDD0-Z", 6)) strcpy(devPath, "\\Device\\Harddisk1\\Partition5"); else { strcpy(devPath, mountPath); return; } char* delim = strchr(mountPath, '\\'); if (delim != NULL) { strcat(devPath, delim); } } bool LaunchXbeA(const char* pathOrDir) { if (!pathOrDir || !pathOrDir[0]){ SetLastError(ERROR_INVALID_PARAMETER); return false; } // Compute directory+file to mount/launch char dir[512]; dir[0]=0; char file[256]; file[0]=0; if (HasXbeExt(pathOrDir)){ // path = "...\.xbe" const char* slash = strrchr(pathOrDir, '\\'); _snprintf(file, sizeof(file), "%s", slash ? slash+1 : pathOrDir); file[sizeof(file)-1]=0; _snprintf(dir, sizeof(dir), "%s", pathOrDir); dir[sizeof(dir)-1]=0; ParentPath(dir); EnsureTrailingSlash(dir, sizeof(dir)); } else { // path = folder: launch default.xbe inside it _snprintf(dir, sizeof(dir), "%s", pathOrDir); dir[sizeof(dir)-1]=0; EnsureTrailingSlash(dir, sizeof(dir)); _snprintf(file, sizeof(file), "default.xbe"); } // Pre-check file exists before remapping D: char pre[512]; JoinPath(pre, sizeof(pre), dir, file); if (GetFileAttributesA(pre) == INVALID_FILE_ATTRIBUTES){ SetLastError(ERROR_FILE_NOT_FOUND); return false; } char devPath[512]; GetDevicePathFromMountedPath(devPath, dir); if (devPath[strlen(devPath) - 1] == '\\') devPath[strlen(devPath) - 1] = '\0'; // Remove trailing slash // Repoint D: to device path of 'dir' char dosD[16]; MakeDosString(dosD, sizeof(dosD), "D:"); STRING sDos; BuildString(sDos, dosD); IoDeleteSymbolicLink(&sDos); // ignore result STRING sDev; BuildString(sDev, devPath); LONG st = IoCreateSymbolicLink(&sDos, &sDev); if (st != 0){ SetLastError(ERROR_ACCESS_DENIED); return false; } // STATUS_SUCCESS == 0 // Launch D:\ char launchPath[512]; _snprintf(launchPath, sizeof(launchPath), "D:\\%s", file); launchPath[sizeof(launchPath)-1]=0; DWORD rc = XLaunchNewImageA(launchPath, (PLAUNCH_DATA)NULL); if (rc == ERROR_SUCCESS) return true; SetLastError(rc); return false; } bool FileExistsA(const char* path) { DWORD a = GetFileAttributesA(path); return (a != INVALID_FILE_ATTRIBUTES) && !(a & FILE_ATTRIBUTE_DIRECTORY); } bool WriteAllA(const char* path, const void* data, DWORD size) { HANDLE h = CreateFileA(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (h == INVALID_HANDLE_VALUE) return false; DWORD wrote = 0; BOOL ok = WriteFile(h, data, size, &wrote, NULL); CloseHandle(h); return ok && wrote == size; } static bool FormatDeviceFatx(const char* devicePath, unsigned long bytesPerCluster) { if (!devicePath || !devicePath[0]) { SetLastError(ERROR_INVALID_PARAMETER); return false; } if (bytesPerCluster == 0) bytesPerCluster = 16 * 1024; // default for cache // Manual init (avoid RtlInitAnsiString to keep header surface small) STRING vol; vol.Buffer = (PCHAR)devicePath; vol.Length = (USHORT)strlen(devicePath); vol.MaximumLength = vol.Length + 1; // Nonzero on success per XDK BOOL ok = XapiFormatFATVolumeEx(&vol, bytesPerCluster); if (!ok) return false; return true; } bool FormatCacheXYZ(unsigned long bytesPerCluster) { // Try to format HDD0 and keep error bool okX = FormatDeviceFatx("\\Device\\Harddisk0\\Partition3", bytesPerCluster); bool okY = FormatDeviceFatx("\\Device\\Harddisk0\\Partition4", bytesPerCluster); bool okZ = FormatDeviceFatx("\\Device\\Harddisk0\\Partition5", bytesPerCluster); // Try to format HDD1 and throw away error FormatDeviceFatx("\\Device\\Harddisk1\\Partition3", bytesPerCluster); FormatDeviceFatx("\\Device\\Harddisk1\\Partition4", bytesPerCluster); FormatDeviceFatx("\\Device\\Harddisk1\\Partition5", bytesPerCluster); return okX && okY && okZ; }