-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
228 additions
and
155 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,199 +2,188 @@ | |
// SPDX-FileCopyrightText: 2023 Dustin Holden <[email protected]> | ||
// SPDX-License-Identifier: GPL-2.0 | ||
// Based on attach.c from driveimageutils by rmenhal | ||
#include <xboxkrnl/xboxkrnl.h> | ||
#include <stdio.h> | ||
#include <string.h> | ||
#include <hal/xbox.h> | ||
#include <xboxkrnl/xboxkrnl.h> | ||
#include <windows.h> | ||
#include "attach.h" | ||
#include "helpers.h" | ||
|
||
// Device name | ||
ANSI_STRING VirtualDeviceName = RTL_CONSTANT_STRING("\\Device\\CdRom1"); | ||
|
||
// Virtual disc image slice data | ||
CHAR VirtualFilePath[8][MAX_PATH] = { 0 }; | ||
|
||
ATTACH_SLICE_DATA AttachSliceData = { | ||
.NumberOfSlices = 0, | ||
.Files = { | ||
{ .Buffer = VirtualFilePath[0], .MaximumLength = sizeof(VirtualFilePath[0]) }, | ||
{ .Buffer = VirtualFilePath[1], .MaximumLength = sizeof(VirtualFilePath[1]) }, | ||
{ .Buffer = VirtualFilePath[2], .MaximumLength = sizeof(VirtualFilePath[2]) }, | ||
{ .Buffer = VirtualFilePath[3], .MaximumLength = sizeof(VirtualFilePath[3]) }, | ||
{ .Buffer = VirtualFilePath[4], .MaximumLength = sizeof(VirtualFilePath[4]) }, | ||
{ .Buffer = VirtualFilePath[5], .MaximumLength = sizeof(VirtualFilePath[5]) }, | ||
{ .Buffer = VirtualFilePath[6], .MaximumLength = sizeof(VirtualFilePath[6]) }, | ||
{ .Buffer = VirtualFilePath[7], .MaximumLength = sizeof(VirtualFilePath[7]) }, | ||
} | ||
}; | ||
|
||
#define MAX_PATHNAME 256 | ||
|
||
int has_iso_extension(unsigned int len, char *str) { | ||
ANSI_STRING tail; | ||
char extension[] = ".iso"; | ||
ANSI_STRING ext_str = { sizeof(extension) - 1, sizeof(extension), extension }; | ||
|
||
if(len < sizeof(extension) - 1) | ||
return FALSE; | ||
|
||
tail.Length = sizeof(extension) - 1; | ||
tail.MaximumLength = tail.Length; | ||
tail.Buffer = str + len - sizeof(extension) + 1; | ||
|
||
return (RtlCompareString(&tail, &ext_str, TRUE) == 0); | ||
} | ||
|
||
int has_cso_extension(unsigned int len, char *str) { | ||
ANSI_STRING tail; | ||
char extension[] = ".cso"; | ||
ANSI_STRING ext_str = { sizeof(extension) - 1, sizeof(extension), extension }; | ||
|
||
if(len < sizeof(extension) - 1) | ||
return FALSE; | ||
|
||
tail.Length = sizeof(extension) - 1; | ||
tail.MaximumLength = tail.Length; | ||
tail.Buffer = str + len - sizeof(extension) + 1; | ||
|
||
return (RtlCompareString(&tail, &ext_str, TRUE) == 0); | ||
} | ||
|
||
int compare_string_tails(PANSI_STRING str1, PANSI_STRING str2, WORD skip) { | ||
ANSI_STRING n1, n2; | ||
|
||
if(str1->Length <= skip) | ||
return(str2->Length <= skip) ? 0 : -1; | ||
else if(str2->Length <= skip) | ||
return 1; | ||
|
||
n1.Length = str1->Length - skip; | ||
n1.MaximumLength = n1.Length; | ||
n1.Buffer = str1->Buffer + skip; | ||
|
||
n2.Length = str2->Length - skip; | ||
n2.MaximumLength = n2.Length; | ||
n2.Buffer = str2->Buffer + skip; | ||
int main(void) __attribute__((optnone)) { | ||
// Make a copy of the XBE launch path. | ||
ANSI_STRING SearchPath = *XeImageFileName; | ||
|
||
return RtlCompareString(&n1, &n2, TRUE); | ||
} | ||
// Determine the last backslash in the search path. | ||
PCHAR LastBackslash = StrRChr(&SearchPath, '\\'); | ||
|
||
int main(void) { | ||
char info_buf[sizeof(FILE_DIRECTORY_INFORMATION) + MAX_PATHNAME]; | ||
// Check if the last backslash was found. | ||
if(LastBackslash == NULL) | ||
goto CleanupAndExit; | ||
|
||
char path[MAX_PATHNAME] = { 0 }; | ||
strncpy(path, XeImageFileName->Buffer, | ||
XeImageFileName->Length < (MAX_PATHNAME - 1) ? XeImageFileName->Length : (MAX_PATHNAME - 1)); | ||
// Update the search path to only include the full device path. | ||
SearchPath.Length = (USHORT)(LastBackslash - SearchPath.Buffer + 1); | ||
|
||
int pathlen = strrchr(path, '\\') - path + 1; | ||
path[pathlen] = '\0'; | ||
// Sanity check the length of the search path. | ||
if(SearchPath.Length < sizeof("\\Device\\") || SearchPath.Length > (MAX_PATH - 4)) | ||
goto CleanupAndExit; | ||
|
||
ANSI_STRING dir_name; | ||
RtlInitAnsiString(&dir_name, path); | ||
|
||
OBJECT_ATTRIBUTES obj_attr = { | ||
.RootDirectory = NULL, | ||
.ObjectName = &dir_name, | ||
// Open the directory. | ||
OBJECT_ATTRIBUTES ObjectAttributes = { | ||
.ObjectName = &SearchPath, | ||
.Attributes = OBJ_CASE_INSENSITIVE, | ||
}; | ||
|
||
IO_STATUS_BLOCK io_status; | ||
HANDLE h; | ||
NTSTATUS status = NtOpenFile(&h, GENERIC_READ | SYNCHRONIZE, &obj_attr, &io_status, | ||
FILE_SHARE_READ, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT); | ||
|
||
if(!NT_SUCCESS(status)) | ||
HalReturnToFirmware(HalQuickRebootRoutine); | ||
IO_STATUS_BLOCK IoStatusBlock; | ||
HANDLE Handle; | ||
|
||
void *membuf = NULL; | ||
unsigned long membuf_size = 1024 * 1024; | ||
NTSTATUS Status = NtOpenFile(&Handle, FILE_LIST_DIRECTORY | SYNCHRONIZE, &ObjectAttributes, &IoStatusBlock, | ||
FILE_SHARE_READ, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT); | ||
|
||
status = NtAllocateVirtualMemory(&membuf, 0, &membuf_size, MEM_COMMIT | MEM_NOZERO, PAGE_READWRITE); | ||
if(!NT_SUCCESS(Status)) | ||
goto CleanupAndExit; | ||
|
||
if(!NT_SUCCESS(status)) | ||
HalReturnToFirmware(HalQuickRebootRoutine); | ||
// Query the directory for the first file. | ||
// TODO: Can we use the FileName mask here to filter out only the files we're interested in? | ||
FileInfo FileInformation = { 0 }; | ||
Status = NtQueryDirectoryFile(Handle, NULL, NULL, NULL, &IoStatusBlock, &FileInformation, sizeof(FileInformation), | ||
FileDirectoryInformation, NULL, TRUE); | ||
|
||
ATTACH_SLICE_DATA *asd = (ATTACH_SLICE_DATA *)membuf; | ||
asd->num_slices = 0; | ||
// Loop through all files in the directory. | ||
while(TRUE) { | ||
// Check if we've reached the end of the directory. | ||
if(Status == STATUS_NO_MORE_FILES) | ||
break; | ||
|
||
char *strbuf = (char *)membuf + sizeof(ATTACH_SLICE_DATA); | ||
// Check if an error occurred. | ||
if(!NT_SUCCESS(Status)) { | ||
NtClose(Handle); | ||
goto CleanupAndExit; | ||
} | ||
|
||
PFILE_DIRECTORY_INFORMATION dir_info; | ||
BOOLEAN first = TRUE; | ||
for(;;) { | ||
if(first || dir_info->NextEntryOffset == 0) { | ||
status = NtQueryDirectoryFile(h, NULL, NULL, NULL, &io_status, | ||
info_buf, sizeof(info_buf), FileDirectoryInformation, NULL, first); | ||
// Skip directories. | ||
if(FileInformation.DirectoryInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) | ||
goto QueryNextFile; | ||
|
||
if(status == STATUS_NO_MORE_FILES) | ||
break; | ||
|
||
first = FALSE; | ||
dir_info = (PFILE_DIRECTORY_INFORMATION)info_buf; | ||
} else | ||
dir_info = (PFILE_DIRECTORY_INFORMATION)((char *)dir_info + dir_info->NextEntryOffset); | ||
// Null-terminate the file name. | ||
ANSI_STRING FileName = { | ||
.Length = FileInformation.DirectoryInfo.FileNameLength, | ||
.MaximumLength = FileInformation.DirectoryInfo.FileNameLength, | ||
.Buffer = FileInformation.DirectoryInfo.FileName, | ||
}; | ||
|
||
if(!has_iso_extension(dir_info->FileNameLength, dir_info->FileName) && | ||
!has_cso_extension(dir_info->FileNameLength, dir_info->FileName)) | ||
continue; | ||
// Check if the file is a supported virtual disc image. | ||
if(!IsFileDiscImage(&FileName)) | ||
goto QueryNextFile; | ||
|
||
ANSI_STRING new_file = { | ||
.Length = pathlen + dir_info->FileNameLength, | ||
.MaximumLength = new_file.Length, | ||
.Buffer = strbuf | ||
// Build the slice path string by concatenating the search path and the file name. | ||
ANSI_STRING SlicePath = { | ||
.Length = 0, | ||
.MaximumLength = sizeof(VirtualFilePath[0]), | ||
.Buffer = VirtualFilePath[AttachSliceData.NumberOfSlices] | ||
}; | ||
|
||
memcpy(strbuf, path, pathlen); | ||
memcpy(strbuf + pathlen, dir_info->FileName, dir_info->FileNameLength); | ||
strbuf += new_file.Length; | ||
// NOTE: We've already checked that the search path string is < MAX_PATH, so no need to check it here. | ||
RtlCopyString(&SlicePath, &SearchPath); | ||
|
||
int i; | ||
for(i = 0; i < asd->num_slices; i++) { | ||
if(compare_string_tails(&new_file, &asd->slice_files[i], pathlen) < 0) | ||
break; | ||
Status = RtlAppendStringToString(&SlicePath, &FileName); | ||
if(!NT_SUCCESS(Status)) { | ||
NtClose(Handle); | ||
goto CleanupAndExit; | ||
} | ||
|
||
RtlMoveMemory(&asd->slice_files[i] + 1, &asd->slice_files[i], (asd->num_slices - i) * sizeof(ANSI_STRING)); | ||
// Determine the slice index so the slices are stored in alphabetical order. | ||
DWORD Index; | ||
for(Index = 0; Index < AttachSliceData.NumberOfSlices; Index++) { | ||
if(CompareStringTails(&SlicePath, &AttachSliceData.Files[Index], SearchPath.Length) < 0) | ||
break; | ||
} | ||
|
||
asd->slice_files[i] = new_file; | ||
// Move the slice data so the new slice can be inserted at the correct position. | ||
RtlMoveMemory(&AttachSliceData.Files[Index] + 1, &AttachSliceData.Files[Index], | ||
(AttachSliceData.NumberOfSlices - Index) * sizeof(ANSI_STRING)); | ||
|
||
if(++asd->num_slices >= MAX_IMAGE_SLICES) | ||
break; | ||
} | ||
// Store the slice data. | ||
AttachSliceData.Files[Index] = SlicePath; | ||
|
||
NtClose(h); | ||
// Increment the number of slices. | ||
AttachSliceData.NumberOfSlices++; | ||
|
||
ANSI_STRING dev_name; | ||
RtlInitAnsiString(&dev_name, "\\Device\\CdRom1"); | ||
// Check if we've reached the maximum number of slices. | ||
if(AttachSliceData.NumberOfSlices >= MAX_IMAGE_SLICES) | ||
break; | ||
|
||
obj_attr.RootDirectory = NULL; | ||
obj_attr.ObjectName = &dev_name; | ||
obj_attr.Attributes = OBJ_CASE_INSENSITIVE; | ||
QueryNextFile: | ||
RtlZeroMemory(&FileInformation, sizeof(FileInformation)); | ||
Status = NtQueryDirectoryFile(Handle, NULL, NULL, NULL, &IoStatusBlock, &FileInformation, | ||
sizeof(FileInformation), FileDirectoryInformation, NULL, FALSE); | ||
} | ||
|
||
status = NtOpenFile(&h, GENERIC_READ | SYNCHRONIZE, &obj_attr, &io_status, | ||
FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT); | ||
// Close the directory handle. | ||
NtClose(Handle); | ||
|
||
if(!NT_SUCCESS(status)) | ||
HalReturnToFirmware(HalQuickRebootRoutine); | ||
// Open the virtual device. | ||
OBJECT_ATTRIBUTES DeviceObjectAttributes = { | ||
.ObjectName = &VirtualDeviceName, | ||
.Attributes = OBJ_CASE_INSENSITIVE, | ||
}; | ||
|
||
status = NtDeviceIoControlFile(h, NULL, NULL, NULL, &io_status, IOCTL_VIRTUAL_CDROM_DETACH, NULL, 0, NULL, 0); | ||
Status = NtOpenFile(&Handle, GENERIC_READ | SYNCHRONIZE, &DeviceObjectAttributes, &IoStatusBlock, FILE_SHARE_READ, | ||
FILE_SYNCHRONOUS_IO_NONALERT); | ||
|
||
/* Note that opening the handle will also mount a file system. All access | ||
* via the handle will go through the file system driver. Ideally we should | ||
* first dismount any possibly mounted file system and then detach the virtual | ||
* disc. It would be nice if we could open a direct access to the device driver, | ||
* but this doesn't seem to be possible with the Xbox kernel API (opening with | ||
* limited access rights doesn't yield the result.) | ||
* | ||
* We will just close the handle and dismount the automatically mounted file | ||
* system. | ||
*/ | ||
NtClose(h); | ||
status = IoDismountVolumeByName(&dev_name); | ||
if(!NT_SUCCESS(Status)) | ||
goto CleanupAndExit; | ||
|
||
status = NtOpenFile(&h, GENERIC_READ | SYNCHRONIZE, &obj_attr, &io_status, | ||
FILE_SHARE_READ, | ||
FILE_SYNCHRONOUS_IO_NONALERT); | ||
Status = NtDeviceIoControlFile(Handle, NULL, NULL, NULL, &IoStatusBlock, IOCTL_VIRTUAL_CDROM_DETACH, NULL, 0, NULL, 0); | ||
|
||
if(!NT_SUCCESS(status)) | ||
HalReturnToFirmware(HalQuickRebootRoutine); | ||
// Note that opening the handle will also mount a file system. All access via the handle will go through the file | ||
// system driver. Ideally we should first dismount any possibly mounted file system and then detach the virtual | ||
// disc. It would be nice if we could open a direct access to the device driver, but this doesn't seem to be possible | ||
// with the Xbox kernel API (opening with limited access rights doesn't yield the result.) | ||
// | ||
// We will just close the handle and dismount the automatically mounted file system. | ||
NtClose(Handle); | ||
Status = IoDismountVolumeByName(&VirtualDeviceName); | ||
|
||
status = NtDeviceIoControlFile(h, NULL, NULL, NULL, &io_status, | ||
IOCTL_VIRTUAL_CDROM_ATTACH, | ||
asd, sizeof(ATTACH_SLICE_DATA), NULL, 0); | ||
Status = NtOpenFile(&Handle, GENERIC_READ | SYNCHRONIZE, &DeviceObjectAttributes, &IoStatusBlock, FILE_SHARE_READ, | ||
FILE_SYNCHRONOUS_IO_NONALERT); | ||
|
||
NtClose(h); | ||
if(!NT_SUCCESS(Status)) { | ||
NtClose(Handle); | ||
goto CleanupAndExit; | ||
} | ||
|
||
/* Also note that access via our second file handle goes through the raw | ||
* file system (there's no other choice since the virtual disc was detached). | ||
* It's still mounted at this point. If we now want to access the attached | ||
* image, we will first need to dismount that file system. | ||
* | ||
* It doesn't matter for this attach.xbe, but do it anyway so we won't forget | ||
* about it in any future application. | ||
*/ | ||
status = IoDismountVolumeByName(&dev_name); | ||
Status = NtDeviceIoControlFile(Handle, NULL, NULL, NULL, &IoStatusBlock, IOCTL_VIRTUAL_CDROM_ATTACH, | ||
(PVOID)&AttachSliceData, sizeof(AttachSliceData), NULL, 0); | ||
NtClose(Handle); | ||
|
||
HalReturnToFirmware(HalQuickRebootRoutine); | ||
// Also note that access via our second file handle goes through the raw file system (there's no other choice since | ||
// the virtual disc was detached). | ||
// It's still mounted at this point. If we now want to access the attached image, we will first need to dismount | ||
// that file system. | ||
// It doesn't matter for this attach.xbe, but do it anyway so we won't forget about it in any future application. | ||
Status = IoDismountVolumeByName(&VirtualDeviceName); | ||
|
||
CleanupAndExit: | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.