I have to modify a text in a file programmatically with C#. What's the best way to do this and avoid rewriting the entire file content. I want to edit only some words and save it.
As others have said, you can easily append to an existing file. Inserting data in the middle of the file requires that you write out the remainder of the file. There's no way to just shift everything in the file. If you want to overwrite individual bytes in place, you can open a FileStream. You then use the "Seek" to go to the specific bytes you want to overwrite, and use Write, or WriteByte to overwrite the existing data with new data.
You can append to an existing file using StreamWriter and AppendText easily.
If you want to do more serious modifications, then I think you must read the contents of the file into memory in C# using StreamReader for example, modifying the contents programmatically of that stream, and then re-writing the file with the stream's contents.
I think that's how it is anyway.
If you are targeting windows, you can use the winapi CreateFile, ReadFile, WriteFile, etc. You can easily write to any spot in a file and write as much data as you want. You do not have to rewrite the file. This doesn't even require unsafe code. I limited the functionality of the functions (no asynch or wacky file mappings) just to get this done quickly, but it can read and write files no problem. To get this example to work, make a small text file 100 bytes or so, and make sure it is in the location of the exe. A little bit of this code comes from the MSDN website. I have modified it significantly though.
using System;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Windows.Forms;
using Path = System.IO.Path;
class Program {
static void Main() {
string sTestFile = Path.Combine(Path.GetDirectoryName(
Application.ExecutablePath), "Test.txt");
// test reading a file
WinApiFile File = new WinApiFile(sTestFile,
WinApiFile.DesiredAccess.GENERIC_READ);
byte[] aBuffer = new byte[1000];
uint cbRead = File.Read(aBuffer, 1000);
File.Close();
// Test writing the file
File.Open(WinApiFile.DesiredAccess.GENERIC_WRITE);
// write the time to a 10 byte offset in the file
// conver the date time to byte array
int i = 0;
foreach (var ch in DateTime.Now.ToString())
aBuffer[i++] = (byte)ch;
// now write it out
File.MoveFilePointer(10); // 10 byte offset
File.Write(aBuffer, (uint)i); // write the time
File.Dispose();
}
}
public class WinApiFile : IDisposable {
/* ---------------------------------------------------------
* private members
* ------------------------------------------------------ */
private SafeFileHandle _hFile = null;
private string _sFileName = "";
private bool _fDisposed;
/* ---------------------------------------------------------
* properties
* ------------------------------------------------------ */
public bool IsOpen { get { return (_hFile != null); } }
public SafeFileHandle Handle { get { return _hFile; } }
public string FileName {
get { return _sFileName; }
set {
_sFileName = (value ?? "").Trim();
if (_sFileName.Length == 0)
CloseHandle(_hFile);
}
}
public int FileLength {
get {
return (_hFile != null) ? (int)GetFileSize(_hFile,
IntPtr.Zero) : 0;
}
set {
if (_hFile == null)
return;
MoveFilePointer(value, MoveMethod.FILE_BEGIN);
if (!SetEndOfFile(_hFile))
ThrowLastWin32Err();
}
}
/* ---------------------------------------------------------
* Constructors
* ------------------------------------------------------ */
public WinApiFile(string sFileName,
DesiredAccess fDesiredAccess) {
FileName = sFileName;
Open(fDesiredAccess);
}
public WinApiFile(string sFileName,
DesiredAccess fDesiredAccess,
CreationDisposition fCreationDisposition) {
FileName = sFileName;
Open(fDesiredAccess, fCreationDisposition);
}
/* ---------------------------------------------------------
* Open/Close
* ------------------------------------------------------ */
public void Open(
DesiredAccess fDesiredAccess) {
Open(fDesiredAccess, CreationDisposition.OPEN_EXISTING);
}
public void Open(
DesiredAccess fDesiredAccess,
CreationDisposition fCreationDisposition) {
ShareMode fShareMode;
if (fDesiredAccess == DesiredAccess.GENERIC_READ) {
fShareMode = ShareMode.FILE_SHARE_READ;
} else {
fShareMode = ShareMode.FILE_SHARE_NONE;
}
Open(fDesiredAccess, fShareMode, fCreationDisposition, 0);
}
public void Open(
DesiredAccess fDesiredAccess,
ShareMode fShareMode,
CreationDisposition fCreationDisposition,
FlagsAndAttributes fFlagsAndAttributes) {
if (_sFileName.Length == 0)
throw new ArgumentNullException("FileName");
_hFile = CreateFile(_sFileName, fDesiredAccess, fShareMode,
IntPtr.Zero, fCreationDisposition, fFlagsAndAttributes,
IntPtr.Zero);
if (_hFile.IsInvalid) {
_hFile = null;
ThrowLastWin32Err();
}
_fDisposed = false;
}
public void Close() {
if (_hFile == null)
return;
_hFile.Close();
_hFile = null;
_fDisposed = true;
}
/* ---------------------------------------------------------
* Move file pointer
* ------------------------------------------------------ */
public void MoveFilePointer(int cbToMove) {
MoveFilePointer(cbToMove, MoveMethod.FILE_CURRENT);
}
public void MoveFilePointer(int cbToMove,
MoveMethod fMoveMethod) {
if (_hFile != null)
if (SetFilePointer(_hFile, cbToMove, IntPtr.Zero,
fMoveMethod) == INVALID_SET_FILE_POINTER)
ThrowLastWin32Err();
}
public int FilePointer {
get {
return (_hFile != null) ? (int)SetFilePointer(_hFile, 0,
IntPtr.Zero, MoveMethod.FILE_CURRENT) : 0;
}
set {
MoveFilePointer(value);
}
}
/* ---------------------------------------------------------
* Read and Write
* ------------------------------------------------------ */
public uint Read(byte[] buffer, uint cbToRead) {
// returns bytes read
uint cbThatWereRead = 0;
if (!ReadFile(_hFile, buffer, cbToRead,
ref cbThatWereRead, IntPtr.Zero))
ThrowLastWin32Err();
return cbThatWereRead;
}
public uint Write(byte[] buffer, uint cbToWrite) {
// returns bytes read
uint cbThatWereWritten = 0;
if (!WriteFile(_hFile, buffer, cbToWrite,
ref cbThatWereWritten, IntPtr.Zero))
ThrowLastWin32Err();
return cbThatWereWritten;
}
/* ---------------------------------------------------------
* IDisposable Interface
* ------------------------------------------------------ */
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool fDisposing) {
if (!_fDisposed) {
if (fDisposing) {
if (_hFile != null)
_hFile.Dispose();
_fDisposed = true;
}
}
}
~WinApiFile() {
Dispose(false);
}
/* ---------------------------------------------------------
* WINAPI STUFF
* ------------------------------------------------------ */
private void ThrowLastWin32Err() {
Marshal.ThrowExceptionForHR(
Marshal.GetHRForLastWin32Error());
}
[Flags]
public enum DesiredAccess : uint {
GENERIC_READ = 0x80000000,
GENERIC_WRITE = 0x40000000
}
[Flags]
public enum ShareMode : uint {
FILE_SHARE_NONE = 0x0,
FILE_SHARE_READ = 0x1,
FILE_SHARE_WRITE = 0x2,
FILE_SHARE_DELETE = 0x4,
}
public enum MoveMethod : uint {
FILE_BEGIN = 0,
FILE_CURRENT = 1,
FILE_END = 2
}
public enum CreationDisposition : uint {
CREATE_NEW = 1,
CREATE_ALWAYS = 2,
OPEN_EXISTING = 3,
OPEN_ALWAYS = 4,
TRUNCATE_EXSTING = 5
}
[Flags]
public enum FlagsAndAttributes : uint {
FILE_ATTRIBUTES_ARCHIVE = 0x20,
FILE_ATTRIBUTE_HIDDEN = 0x2,
FILE_ATTRIBUTE_NORMAL = 0x80,
FILE_ATTRIBUTE_OFFLINE = 0x1000,
FILE_ATTRIBUTE_READONLY = 0x1,
FILE_ATTRIBUTE_SYSTEM = 0x4,
FILE_ATTRIBUTE_TEMPORARY = 0x100,
FILE_FLAG_WRITE_THROUGH = 0x80000000,
FILE_FLAG_OVERLAPPED = 0x40000000,
FILE_FLAG_NO_BUFFERING = 0x20000000,
FILE_FLAG_RANDOM_ACCESS = 0x10000000,
FILE_FLAG_SEQUENTIAL_SCAN = 0x8000000,
FILE_FLAG_DELETE_ON = 0x4000000,
FILE_FLAG_POSIX_SEMANTICS = 0x1000000,
FILE_FLAG_OPEN_REPARSE_POINT = 0x200000,
FILE_FLAG_OPEN_NO_CALL = 0x100000
}
public const uint INVALID_HANDLE_VALUE = 0xFFFFFFFF;
public const uint INVALID_SET_FILE_POINTER = 0xFFFFFFFF;
// Use interop to call the CreateFile function.
// For more information about CreateFile,
// see the unmanaged MSDN reference library.
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern SafeFileHandle CreateFile(
string lpFileName,
DesiredAccess dwDesiredAccess,
ShareMode dwShareMode,
IntPtr lpSecurityAttributes,
CreationDisposition dwCreationDisposition,
FlagsAndAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("kernel32", SetLastError = true)]
internal static extern Int32 CloseHandle(
SafeFileHandle hObject);
[DllImport("kernel32", SetLastError = true)]
internal static extern bool ReadFile(
SafeFileHandle hFile,
Byte[] aBuffer,
UInt32 cbToRead,
ref UInt32 cbThatWereRead,
IntPtr pOverlapped);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool WriteFile(
SafeFileHandle hFile,
Byte[] aBuffer,
UInt32 cbToWrite,
ref UInt32 cbThatWereWritten,
IntPtr pOverlapped);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern UInt32 SetFilePointer(
SafeFileHandle hFile,
Int32 cbDistanceToMove,
IntPtr pDistanceToMoveHigh,
MoveMethod fMoveMethod);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetEndOfFile(
SafeFileHandle hFile);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern UInt32 GetFileSize(
SafeFileHandle hFile,
IntPtr pFileSizeHigh);
}
When you open a file for writing there are only two possible outcomes. Either you open it for appending which will allow you to write at the end only, or you open it for normal write in which case you will write from the beginning of the file. There is no "open at mid point, write then shift remaining content according to insertion length".
It is possible that some API will allow such modifications but behind the scenes, they will only allow you those two basic operations. You shouldn't worry to much about rewriting the whole file however, it is not such a bad operation unless your file is huge and your hard drive is very slow. You should worry more about the number of times you do it instead.
Open the file
Read the whole file into a variable or array
Insert modifications
Write back the file
It's probably simpler to read the file in and modify it then read it out again.
To really be efficient (i.e. not read/write the whole file for every operation) you need to do some house keeping to keep track of metadata about the file, and then do binary editing.
You'll still have to manage the case where the file grows and needs to be appended, etc.
Related
I am reading directly from a disk using C# and pinvoking the kernel32 ReadFile method.i want just read a particular sector for save time but ReadFile read from first to N sector. How can read only own sector with my choice?
[StructLayout(LayoutKind.Sequential)]
public struct OVERLAPPED
{
public uint Internal;
public uint InternalHigh;
public uint Offset;
public uint OffsetHigh;
public int hEvent;
}
[DllImport("kernel32", SetLastError = true)]
static extern int CreateFile(string filename, uint desiredAccess, uint shareMode, IntPtr attributes, uint creationDisposition, uint flagsAndAttributes, IntPtr templateFile);
[DllImport("kernel32", SetLastError = true)]
public static extern Boolean CloseHandle(int handle);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern Boolean ReadFile(IntPtr hFile, Byte[] buffer, UInt32 BytesToRead, ref UInt32 BytedRead, OVERLAPPED OverLapped);
static int EIGHT_K = 8192;
static int FIVE_TWELVE_BYTES = 512;
static uint GENERIC_READ = 0x80000000;
static uint OPEN_EXISTING = 3;
static uint FILE_SHARE_READ = 1;
static uint FILE_SHARE_WRITE = 2;
[STAThread]
private void button1_Click(object sender, EventArgs e)
{
int fileHandle = 0;
bool returnVal = true;
try
{
// Open the device specified (Using the boot partition)
string deviceName = #"\\.\f:";
fileHandle = CreateFile(deviceName, GENERIC_READ,FILE_SHARE_READ | FILE_SHARE_WRITE, (IntPtr)0, OPEN_EXISTING, 0,(IntPtr)0);
if (fileHandle != -1)
{
Byte[] sector = new Byte[EIGHT_K];
UInt32 bytesRead = (uint)EIGHT_K;
OVERLAPPED ol = new OVERLAPPED();
// Can't get a FileStream ctor to work so I am using Win32 API ReadFile
bool worked = ReadFile((IntPtr)fileHandle, sector, (uint)EIGHT_K, ref bytesRead, ol);
return;
}
}
catch (Exception ex)
{
return;
}
finally
{
CloseHandle(fileHandle);
}
return;
}
I want to mark the DVD till required Original DVD to run the program.
Your OVERLAPPED struct is declared poorly and is incorrect in a 64 bit process. But in any case you don't need it. You are not performing overlapped I/O. Which is just as well because the declaration of ReadFile is incorrect. That function wants a pointer to an OVERLAPPED struct. You pass it by value.
In any case, you just don't need to consider overlapped I/O. So fix this issue by deleting the OVERLAPPED struct declaration from your code. And declare ReadFile like this:
[DllImport("kernel32.dll", SetLastError = true)]
public static extern Boolean ReadFile(IntPtr hFile, Byte[] buffer,
UInt32 BytesToRead, out UInt32 BytedRead, IntPtr Overlapped);
Pass IntPtr.Zero as the Overlapped parameter. And do make sure that you check the return value of ReadFile for an error.
The next step is to seek to a location in the file. Use SetFilePointerEx for that.
DllImport("kernel32.dll")]
static extern bool SetFilePointerEx(IntPtr hFile, long liDistanceToMove,
out long lpNewFilePointer, uint dwMoveMethod);
Consult the documentation for SetFilePointerEx to work out how to call this function.
Since you are using direct disk access, you will of course need to align the reads to sector boundaries.
My project needs to inspect .c and .dll files. it combines those informations to determine what it is supposed to call and then calls that.
I need to inspect the dlls to find which dll has which function. I have gotten so far as to map the dll to memory without initialising it. now i need to map the header to something so i can read out the section that has the callable names in it.
how can i do that? this is the code so far:
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags);
public static string[] GetFKTNames(string dll)
{
IntPtr lib = LoadLibraryEx(dll, IntPtr.Zero, LoadLibraryFlags.DONT_RESOLVE_DLL_REFERENCES);
//INDICATES WHAT I WANT TO DO, BUT DOES NOT WORk
//Header header = GetHeader(lib);
//Unload(lib);
//return header.Names;
}
EDIT #2:
i made a little bit of progress and quit it for today... there are 4 free days coming up here in germany...
i am not entirely sure if that marshalling is correct - i had no way to test it. I would love to read a book on that topic - so please comment if you know a good book that explains how that headerstuff works and what different headers are out there.
private static List<string> ListDLLFunctions(string sADllName)
{
List<string> names = new List<string>();
IntPtr LoadedImage = LoadLibraryEx(sADllName, IntPtr.Zero, LoadLibraryFlags.DONT_RESOLVE_DLL_REFERENCES);
IMAGE_NT_HEADERS header = (IMAGE_NT_HEADERS) Marshal.PtrToStructure(libPtr, typeof(IMAGE_NT_HEADERS));
// ImageExportDirectory = (_IMAGE_EXPORT_DIRECTORY*)
// ImageDirectoryEntryToData(LoadedImage.MappedAddress,
// false, IMAGE_DIRECTORY_ENTRY_EXPORT, &cDirSize);
// if (ImageExportDirectory != NULL)
// {
// dNameRVAs = (DWORD *)ImageRvaToVa(LoadedImage.FileHeader,
// LoadedImage.MappedAddress,
// ImageExportDirectory->AddressOfNames, NULL);
// for(size_t i = 0; i < ImageExportDirectory->NumberOfNames; i++)
// {
// sName = (char *)ImageRvaToVa(LoadedImage.FileHeader,
// LoadedImage.MappedAddress,
// dNameRVAs[i], NULL);
// slListOfDllFunctions.push_back(sName);
// }
// }
FreeLibrary(LoadedImage);
return names;
}
static void Main(string[] args)
{
List<string> names = ListDLLFunctions("KERNEL32.DLL");
}
There isn't a "one call" function that can do it for you.
You have to load the dll and look for the names in the export table.
There is a post with some code to do it in c++ - maybe you can try to port it to c#
Non from the code, but from console I usually use
DumpBin utility to see Dll's exports.
I think you can use that tool with Process.Start(..) and parse its output in order to gain an information you need.
Hope this helps.
I solved the problem for my usecase:
I load the lib into momory, copy it into a bytearray and then investigate it using PE information.
There are a couple of useful ressources for this topic that helped me a lot:
c# import examples for relevant structures
msdn article on PE format
zotteljedi article on PE format
nunu brito article on PE format
simon cooper article on PE format
joachim bauch article on loading dll
In retrospec i get how i could have used the other approaches to get my information. The Class that i have written is great to learn PE and explore some dlls, i recommand rewriting it if you want to get the grip of how the PE works.
Here is the Class:
public class DLLHelper
{
private byte[] dllInMemory;
private UInt32 PESizeOfImage;
private UInt32 VA_PE;
[DllImport("kernel32.dll")]
static public extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags);
[DllImport("kernel32.dll")]
static public extern bool FreeLibrary(IntPtr hModule);
public enum LoadLibraryFlags : uint
{
DONT_RESOLVE_DLL_REFERENCES = 0x00000001,
LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010,
LOAD_LIBRARY_AS_DATAFILE = 0x00000002,
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040,
LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020,
LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008
}
public DLLHelper(string dllpath)
{
PESizeOfImage = GetDllSizeInMemory(dllpath);
dllInMemory = GetDLLCopy(dllpath, PESizeOfImage);
UInt32 VA_p_PE = 0x3C;
VA_PE = Get4ByteFromLocation(VA_p_PE, dllInMemory);
}
private byte[] GetDLLCopy(string dllpath, uint PESizeOfImage)
{
IntPtr libPtr = LoadLibraryEx(dllpath, IntPtr.Zero, LoadLibraryFlags.DONT_RESOLVE_DLL_REFERENCES);
byte[] dllInMemory = new byte[PESizeOfImage];
Marshal.Copy(libPtr, dllInMemory, 0, (int)PESizeOfImage);
FreeLibrary(libPtr);
return dllInMemory;
}
private UInt32 GetDllSizeInMemory(string dllpath)
{
byte[] dllpreload = File.ReadAllBytes(dllpath);
UInt32 pp_PE = 0x3C;
UInt32 p_PE = Get4ByteFromLocation(pp_PE, dllpreload);
UInt32 p_PEOPTIONALHEADER = p_PE + 0x18;
UInt32 p_PESizeOfImage = p_PEOPTIONALHEADER + 0x38;
return Get4ByteFromLocation(p_PESizeOfImage, dllpreload);
}
public void DumpToFile(String filename)
{
File.WriteAllBytes(filename, dllInMemory);
}
public string GetDLLName()
{
UInt32 VAExport = GetVAExport(VA_PE, dllInMemory);
UInt32 VAName = GetVAName(VAExport, dllInMemory);
String Name = GetString(VAName, dllInMemory);
return Name;
}
public List<String> GetFunctionNames()
{
List<String> fkts = new List<String>();
UInt32 VAExport = GetVAExport(VA_PE, dllInMemory);
UInt32 VA_p_firstFKT = GetVA_p_firstFKT(VAExport, dllInMemory);
UInt32 VA_p_lastFKT = GetVA_p_lastFKT(VAExport, dllInMemory);
for (UInt32 VA_p_fkt = VA_p_firstFKT; VA_p_fkt <= VA_p_lastFKT; VA_p_fkt += sizeof(UInt32))
{
UInt32 VA_fkt = Get4ByteFromLocation(VA_p_fkt, dllInMemory);
fkts.Add(GetString(VA_fkt, dllInMemory));
}
return fkts;
}
private UInt32 GetVA_p_lastFKT(UInt32 VAExport, byte[] dllInMemory)
{
UInt32 first = GetVA_p_firstFKT(VAExport, dllInMemory);
UInt32 count = GetfktCount(VAExport, dllInMemory);
UInt32 last = first + (count - 1) * sizeof(UInt32);
return last;
}
private UInt32 GetfktCount(UInt32 VAExport, byte[] dllInMemory)
{
UInt32 RVA_Count = 0x14;
UInt32 VA_Count = VAExport + RVA_Count;
return Get4ByteFromLocation(VA_Count, dllInMemory);
}
private UInt32 GetVA_p_firstFKT(UInt32 VAExport, byte[] dllInMemory)
{
UInt32 RVA_p_FIRST = 0x20;
UInt32 VA_p_FIRST = VAExport + RVA_p_FIRST;
return Get4ByteFromLocation(VA_p_FIRST, dllInMemory);
}
private UInt32 GetVAName(UInt32 VAExport, byte[] dllInMemory)
{
UInt32 RVA_p_NAME = 0x0C;
UInt32 VA_p_NAME = VAExport + RVA_p_NAME;
return Get4ByteFromLocation(VA_p_NAME, dllInMemory);
}
private UInt32 GetVAExport(UInt32 VAPE, byte[] dllInMemory)
{
UInt32 RVA_p_EXPORT = 0x78;
UInt32 VA_p_EXPORT = VAPE + RVA_p_EXPORT;
return Get4ByteFromLocation(VA_p_EXPORT, dllInMemory);
}
string GetString(UInt32 location, byte[] dll)
{
int length = 0;
while (dll[location + length] != 0x00)
{
length++;
}
if (location > int.MaxValue) throw new Exception("uncastable");
return Encoding.UTF8.GetString(dll, (int)location, length);
}
private UInt32 Get4ByteFromLocation(UInt32 location, byte[] dll)
{
if (!(BitConverter.IsLittleEndian))
{
byte[] partial = GetByteSubset(4, location, dll);
Array.Reverse(partial);
return BitConverter.ToUInt32(partial, 0);
}
return BitConverter.ToUInt32(dll, (int)location);
}
private byte[] GetByteSubset(int size, UInt32 location, byte[] dll)
{
byte[] val = new byte[size];
for (int i = 0; i < size; i++)
{
val[i] = dll[location + i];
}
return val;
}
}
How do I programatically change Volume Serial of a Fat32 partition from C#. I found this example, but it is written with C++ which I don't read well. Could someone please answer a C# code snippet?
Update:
I can see the C++ function from above example which I think it's possible to direct port to C#
void CVolumeSerialDlg::ChangeSerialNumber(DWORD Drive, const DWORD newSerial)
{
const max_pbsi = 3;
struct partial_boot_sector_info
{
LPSTR Fs; // file system name
DWORD FsOffs; // offset of file system name in the boot sector
DWORD SerialOffs; // offset of the serialnumber in the boot sector
};
partial_boot_sector_info pbsi[max_pbsi] =
{
{"FAT32", 0x52, 0x43},
{"FAT", 0x36, 0x27},
{"NTFS", 0x03, 0x48}
};
TCHAR szDrive[12];
char Sector[512];
DWORD i;
sprintf(szDrive, "%c:\\", Drive & 0xFF);
if (!disk.Open(szDrive))
{
ShowErrorString("Could not open disk!");
return;
}
// read sector
if (!disk.ReadSector(0, Sector))
{
ShowErrorString("Could not read sector!");
return;
}
// try to search for a valid boot sector
for (i=0;i<max_pbsi;i++)
{
if (strncmp(pbsi[i].Fs, Sector+pbsi[i].FsOffs, strlen(pbsi[i].Fs)) == 0)
{
// we found a valid signature
break;
}
}
if (i >= max_pbsi)
{
MessageBox(_T("Cannot change serial number of this file system!"),
_T("Error"), MB_ICONERROR);
return;
}
// patch serial number
*(PDWORD)(Sector+pbsi[i].SerialOffs) = newSerial;
// write boot sector
if (!disk.WriteSector(0, Sector))
{
ShowErrorString("Could not write sector!");
return;
}
ShowErrorString("Volume serial number changed successfully!\r"
"You might want to restart your system for changes to take effect!");
}
No guarantees, be careful.
void ChangeSerialNumber(char volume, uint newSerial)
{
var fsInfo = new[]
{
new { Name = "FAT32", NameOffs = 0x52, SerialOffs = 0x43 },
new { Name = "FAT", NameOffs = 0x36, SerialOffs = 0x27 },
new { Name = "NTFS", NameOffs = 0x03, SerialOffs = 0x48 }
};
using (var disk = new Disk(volume))
{
var sector = new byte[512];
disk.ReadSector(0, sector);
var fs = fsInfo.FirstOrDefault(
f => Strncmp(f.Name, sector, f.NameOffs)
);
if (fs == null) throw new NotSupportedException("This file system is not supported");
var s = newSerial;
for (int i = 0; i < 4; ++i, s >>= 8) sector[fs.SerialOffs + i] = (byte)(s & 0xFF);
disk.WriteSector(0, sector);
}
}
bool Strncmp(string str, byte[] data, int offset)
{
for(int i = 0; i < str.Length; ++i)
{
if (data[i + offset] != (byte)str[i]) return false;
}
return true;
}
class Disk : IDisposable
{
private SafeFileHandle handle;
public Disk(char volume)
{
var ptr = CreateFile(
String.Format("\\\\.\\{0}:", volume),
FileAccess.ReadWrite,
FileShare.ReadWrite,
IntPtr.Zero,
FileMode.Open,
0,
IntPtr.Zero
);
handle = new SafeFileHandle(ptr, true);
if (handle.IsInvalid) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
public void ReadSector(uint sector, byte[] buffer)
{
if (buffer == null) throw new ArgumentNullException("buffer");
if (SetFilePointer(handle, sector, IntPtr.Zero, EMoveMethod.Begin) == INVALID_SET_FILE_POINTER) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
uint read;
if (!ReadFile(handle, buffer, buffer.Length, out read, IntPtr.Zero)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
if (read != buffer.Length) throw new IOException();
}
public void WriteSector(uint sector, byte[] buffer)
{
if (buffer == null) throw new ArgumentNullException("buffer");
if (SetFilePointer(handle, sector, IntPtr.Zero, EMoveMethod.Begin) == INVALID_SET_FILE_POINTER) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
uint written;
if (!WriteFile(handle, buffer, buffer.Length, out written, IntPtr.Zero)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
if (written != buffer.Length) throw new IOException();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (handle != null) handle.Dispose();
}
}
enum EMoveMethod : uint
{
Begin = 0,
Current = 1,
End = 2
}
const uint INVALID_SET_FILE_POINTER = 0xFFFFFFFF;
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr CreateFile(
string fileName,
[MarshalAs(UnmanagedType.U4)] FileAccess fileAccess,
[MarshalAs(UnmanagedType.U4)] FileShare fileShare,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
int flags,
IntPtr template);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint SetFilePointer(
[In] SafeFileHandle hFile,
[In] uint lDistanceToMove,
[In] IntPtr lpDistanceToMoveHigh,
[In] EMoveMethod dwMoveMethod);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadFile(SafeFileHandle hFile, [Out] byte[] lpBuffer,
int nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
[DllImport("kernel32.dll")]
static extern bool WriteFile(SafeFileHandle hFile, [In] byte[] lpBuffer,
int nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten,
[In] IntPtr lpOverlapped);
}
Use e.g. ChangeSerialNumber('D', 0x12345678);
Here is a small example for reading and writing the serial number of a
FAT32 volume. To keep the sample small all error handling has been omitted.
Please note that direct access to the sectors of a volume
may lead to data loss or corruption. So be careful in using the
example below (it is not an example suitable for production usage).
No guarantee!
In the example below I use the Win32 API GetDiskFreeSpace (using .Net interop) to get
the bytes per sector for the FAT32 volume.
To open the fat volume, I use the Win32 API CreateFile because the FileStream class
does not support opening disk partitions directly.
static uint GenericRead = 0x80000000;
static uint GenericWrite = 0x40000000;
static uint OpenExisting = 3;
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr SecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool GetDiskFreeSpace(string lpRootPathName, out uint lpSectorsPerCluster, out uint lpBytesPerSector, out uint lpNumberOfFreeClusters, out uint lpTotalNumberOfClusters);
static void ReadAndSetSerialNumber()
{
const string driveLetter = "e:"; // Drive with FAT32 file system.
uint sectorsPerCluster;
uint bytesPerSector;
uint numberOfFreeClusters;
uint totalNumberOfClusters;
GetDiskFreeSpace(String.Format(#"{0}\", driveLetter), out sectorsPerCluster, out bytesPerSector,
out numberOfFreeClusters, out totalNumberOfClusters);
Console.Out.WriteLine("Info for drive {0}", driveLetter);
Console.Out.WriteLine("Bytes per sector: {0}", bytesPerSector);
const int fatSerialOffset = 0x43;
const int fatIdOffset = 0x52;
const string fatFileSystemId = "FAT32";
using (SafeFileHandle sfh = CreateFile(String.Format("\\\\.\\{0}", driveLetter), GenericRead | GenericWrite,
(uint)FileShare.ReadWrite, IntPtr.Zero, OpenExisting, 0, IntPtr.Zero))
{
using (FileStream fs = new FileStream(sfh, FileAccess.ReadWrite))
{
byte[] firstSector = new byte[bytesPerSector];
fs.Read(firstSector, 0, (int)bytesPerSector);
if (Encoding.ASCII.GetString(firstSector, fatIdOffset, fatFileSystemId.Length) == fatFileSystemId)
{
Console.Out.WriteLine("FAT32 file system found...");
uint serial = BitConverter.ToUInt32(firstSector, fatSerialOffset);
Console.Out.WriteLine("Read serial number: {0:X4}-{1:X4}", serial >> 16, serial & 0xFFFF);
// Write new serial number.
byte[] newserial = BitConverter.GetBytes((uint)10000123);
Array.Copy(newserial, 0, firstSector, fatSerialOffset, newserial.Length);
fs.Seek(0, SeekOrigin.Begin);
fs.Write(firstSector, 0, (int)bytesPerSector);
}
}
}
}
Furthermore you could use the .Net Framework's DriveInfo class to enumerate the
available drives on your computer.
Hope, this helps.
I'm trying to determine worst case disk speed, so I wrote the following function.
static public decimal MBytesPerSec(string volume)
{
string filename = volume + "\\writetest.tmp";
if (System.IO.File.Exists(filename))
System.IO.File.Delete(filename);
System.IO.StreamWriter file = new System.IO.StreamWriter(filename);
char[] data = new char[64000];
Stopwatch watch = new Stopwatch();
watch.Start();
int i = 0;
for (; i < 1000; i++)
{
file.Write(data);
if (watch.ElapsedMilliseconds > 2000)
{
break;
}
}
watch.Stop();
file.Close();
System.IO.File.Delete(volume + "\\test.txt");
decimal mbytessec = (i * 64 / watch.ElapsedMilliseconds);
return mbytessec;
}
The function works OK, but the writes are getting cached, so the speed is not worst case.
In WIN32 C++, I would simply create the file with FILE_FLAG_NO_BUFFERING, FILE_FLAG_WRITE_THROUGH options, and then make sure to follow the non-cached writing rules (write to the file at sector size offsets, with minimum of 4k writes)
I found one article that discusses the .NET technique.
So I wrote a new function (ignore the math errors).
static public decimal MBytesPerSecNonCached(string volume)
{
const FileOptions FILE_FLAG_NO_BUFFERING = (FileOptions)0x20000000;
string filename = volume + "\\writetest.tmp";
using (FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, 1024, FileOptions.WriteThrough | FILE_FLAG_NO_BUFFERING))
{
byte[] data = new byte[65535];
int i = 0;
Stopwatch watch = new Stopwatch();
watch.Start();
for (; i < 1000; i++)
{
fs.Write(data, 0, 65535);
if (watch.ElapsedMilliseconds > 2000)
{
break;
}
}
watch.Stop();
fs.Close();
System.IO.File.Delete(filename);
decimal mbytessec = (i * 64 / watch.ElapsedMilliseconds);
return mbytessec;
}
}
This function works for 4k, 16K and 32K write sizes, but once I try 64K write sizes, I get an exception:
IO operation will not work. Most likely the file will become too long or the handle was not opened to support synchronous IO operations.
So, how can I fix this so I can test with larger than 32KB write sizes (64KB to 4096KB)?
Try some unmanaged code:
[DllImport("kernel32", SetLastError = true)]
static extern unsafe SafeFileHandle CreateFile(
string FileName, // file name
uint DesiredAccess, // access mode
uint ShareMode, // share mode
IntPtr SecurityAttributes, // Security Attr
uint CreationDisposition, // how to create
uint FlagsAndAttributes, // file attributes
IntPtr hTemplate // template file
);
const uint FILE_FLAG_NO_BUFFERING = 0x20000000;
SafeFileHandle handle = CreateFile("filename",
(uint)FileAccess.Write,
(uint)FileShare.None,
IntPtr.Zero,
(uint)FileMode.Open,
FILE_FLAG_NO_BUFFERING,
IntPtr.Zero);
var unBufferedStream = new FileStream(handle,FileAccess.Read,blockSize,false);
now you should have access to an unbuffered stream which you can read and write however you please with no constraints
For the record....you can also disable caching like this:
[DllImport("KERNEL32", SetLastError = true)]
public extern static int DeviceIoControl(IntPtr hDevice, uint IoControlCode,
IntPtr lpInBuffer, uint InBufferSize,
IntPtr lpOutBuffer, uint nOutBufferSize,
ref uint lpBytesReturned,
IntPtr lpOverlapped);
[DllImport("KERNEL32", SetLastError = true)]
public extern static int CloseHandle(
IntPtr hObject);
[StructLayout(LayoutKind.Sequential)]
public struct DISK_CACHE_INFORMATION
{
public byte ParametersSavable;
public byte ReadCacheEnabled;
public byte WriteCacheEnabled;
public int ReadRetentionPriority;//DISK_CACHE_RETENTION_PRIORITY = enum = int
public int WriteRetentionPriority;//DISK_CACHE_RETENTION_PRIORITY = enum = int
public Int16 DisablePrefetchTransferLength;//WORD
public byte PrefetchScalar;
}
public void SetDiskCache(byte val)
{
IntPtr h = CreateFile("\\\\.\\PHYSICALDRIVE0", (uint)FileAccess.Read | (uint)FileAccess.Write, (uint)FileShare.Write, IntPtr.Zero, (uint)FileMode.Open, 0, IntPtr.Zero);
DISK_CACHE_INFORMATION sInfo = new DISK_CACHE_INFORMATION();
IntPtr ptrout = Marshal.AllocHGlobal(Marshal.SizeOf(sInfo));
Marshal.StructureToPtr(sInfo, ptrout, true);
uint dwWritten = 0;
int ret = DeviceIoControl(h,IOCTL_DISK_GET_CACHE_INFORMATION,IntPtr.Zero,0,ptrout,(uint)Marshal.SizeOf(sInfo),ref dwWritten,IntPtr.Zero);
sInfo = (DISK_CACHE_INFORMATION)Marshal.PtrToStructure(ptrout,typeof(DISK_CACHE_INFORMATION));
sInfo.ReadCacheEnabled = val;
// acuma trimite structura modificata
IntPtr ptrin = Marshal.AllocHGlobal(Marshal.SizeOf(sInfo));
Marshal.StructureToPtr(sInfo, ptrin, true);
ret = DeviceIoControl(h, IOCTL_DISK_SET_CACHE_INFORMATION, ptrin, (uint)Marshal.SizeOf(sInfo), IntPtr.Zero, 0, ref dwWritten, IntPtr.Zero);
CloseHandle(h);
}
I have the ntdll.dll's NtCreateFile() function hooked to allow/deny the access of certain files. Unlike kernel32.dll's CreateFile() which easily gives you the full path to the file in question, ntdll.dll's NtCreateFile() function only gives you the handle to the file. I need to obtain the full path of the file from a file handle, to consequently allow/deny access. I've searched around and there doesn't seem to be a working C# solution.
This solution is in C++, and documented from Microsoft. I've tried to port it over to C# with not much success. Here is my attempt at the C# equivalent of the C++ version of "obtaining a filename from a file handle":
public string GetFileNameFromHandle(IntPtr FileHandle)
{
string fileName = String.Empty;
IntPtr fileMap = IntPtr.Zero, fileSizeHi = IntPtr.Zero;
UInt32 fileSizeLo = 0;
fileSizeLo = GetFileSize(FileHandle, fileSizeHi);
if (fileSizeLo == 0 && fileSizeHi == IntPtr.Zero)
{
// cannot map an 0 byte file
return String.Empty;
}
fileMap = CreateFileMapping(FileHandle, IntPtr.Zero, FileMapProtection.PageReadonly, 0, 1, null);
if (fileMap != IntPtr.Zero)
{
IntPtr pMem = MapViewOfFile(fileMap, FileMapAccess.FileMapRead, 0, 0, 1);
if (pMem != IntPtr.Zero)
{
StringBuilder fn = new StringBuilder(250);
GetMappedFileName(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle, pMem, fn, 250);
if (fileName.Length > 0)
{
UnmapViewOfFile(pMem);
CloseHandle(FileHandle);
return fn.ToString();
}
else
{
UnmapViewOfFile(pMem);
CloseHandle(FileHandle);
return String.Empty;
}
}
}
return String.Empty;
}
I have, of course, all the necessary DLLImports and user-defined types. When I use this function on handles, I get an empty string in return. It's also pretty hard to debug this, since this method is in a DLL that gets injected into a target process, not like something you can set a breakpoint at and enjoy Visual Studio's debugging system. I guess I could write a log file or some trace system, but I'm not that desperate yet. I just need a successful C# version of "get filename from file handle".
Any insight, code fixes, links?
Solved it myself. Here's the working code with the references and stuff.
[DllImport("kernel32.dll")]
static extern uint GetFileSize(IntPtr hFile, IntPtr lpFileSizeHigh);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr CreateFileMapping(
IntPtr hFile,
IntPtr lpFileMappingAttributes,
FileMapProtection flProtect,
uint dwMaximumSizeHigh,
uint dwMaximumSizeLow,
[MarshalAs(UnmanagedType.LPTStr)]string lpName);
[Flags]
public enum FileMapProtection : uint
{
PageReadonly = 0x02,
PageReadWrite = 0x04,
PageWriteCopy = 0x08,
PageExecuteRead = 0x20,
PageExecuteReadWrite = 0x40,
SectionCommit = 0x8000000,
SectionImage = 0x1000000,
SectionNoCache = 0x10000000,
SectionReserve = 0x4000000,
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr MapViewOfFile(
IntPtr hFileMappingObject,
FileMapAccess dwDesiredAccess,
uint dwFileOffsetHigh,
uint dwFileOffsetLow,
uint dwNumberOfBytesToMap);
[Flags]
public enum FileMapAccess : uint
{
FileMapCopy = 0x0001,
FileMapWrite = 0x0002,
FileMapRead = 0x0004,
FileMapAllAccess = 0x001f,
fileMapExecute = 0x0020,
}
[DllImport("psapi.dll", SetLastError = true)]
public static extern uint GetMappedFileName(IntPtr m_hProcess, IntPtr lpv, StringBuilder
lpFilename, uint nSize);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
public static string GetFileNameFromHandle(IntPtr FileHandle)
{
string fileName = String.Empty;
IntPtr fileMap = IntPtr.Zero, fileSizeHi = IntPtr.Zero;
UInt32 fileSizeLo = 0;
fileSizeLo = GetFileSize(FileHandle, fileSizeHi);
if (fileSizeLo == 0)
{
// cannot map an 0 byte file
return "Empty file.";
}
fileMap = CreateFileMapping(FileHandle, IntPtr.Zero, FileMapProtection.PageReadonly, 0, 1, null);
if (fileMap != IntPtr.Zero)
{
IntPtr pMem = MapViewOfFile(fileMap, FileMapAccess.FileMapRead, 0, 0, 1);
if (pMem != IntPtr.Zero)
{
StringBuilder fn = new StringBuilder(250);
GetMappedFileName(System.Diagnostics.Process.GetCurrentProcess().Handle, pMem, fn, 250);
if (fn.Length > 0)
{
UnmapViewOfFile(pMem);
CloseHandle(FileHandle);
return fn.ToString();
}
else
{
UnmapViewOfFile(pMem);
CloseHandle(FileHandle);
return "Empty filename.";
}
}
}
return "Empty filemap handle.";
}
From http://msdn.microsoft.com/en-us/library/aa366789.aspx
"The following example obtains a file name from a handle to a file object using a file mapping object. It uses the CreateFileMapping and MapViewOfFile functions to create the mapping. Next, it uses the GetMappedFileName function to obtain the file name."
Code looks legit to me, hope that helps.
The code you posted here has been copied from the MSDN.
It has several disadvantages: It requires a real file bigger than 0 Bytes to work. It does not work for files of 0 Bytes nor does it work for directories. (and I'm not even talking about network drives)
I have posted a perfectly working code here:
How to get name associated with open HANDLE