Related
Currently I am trying to implement a save function for my RSA key with the help of bouncycastle. I am running into problems if I try to save my public or private key encrypted and load it afterwards.
As a little example here the original public key:
305C300D06092A864886F70D0101010500034B00304802410096B4751049165D1E046063EA22E8FFA0F90AE1DD997A3876DA5F79C7DE97951F009AC9ACA3EB91114F8A32C04F48293B6665CD6DD5C406C81CD13270A2AB61130203010001
What I get after loading it (it adds 4 zeroes, bigger key means more zeroes added):
305C300D06092A864886F70D0101010500034B00304802410096B4751049165D1E046063EA22E8FFA0F90AE1DD997A3876DA5F79C7DE97951F009AC9ACA3EB91114F8A32C04F48293B6665CD6DD5C406C81CD13270A2AB611302030100010000
I found out it has something to do with my implementation of the symmetric encryption and the padding used there. Normal text no matter how long it is just works fine without extra data getting added.
This is the code I am using for my AES encryption:
Encryption
byte[] outputBytes = new byte[0];
AesEngine aesengine = new AesEngine();
CbcBlockCipher aesblockCipher = new CbcBlockCipher(aesengine);
PaddedBufferedBlockCipher aescipher = new PaddedBufferedBlockCipher(aesblockCipher);
KeyParameter aeskeyParameter = new KeyParameter(Hash.HashDataBlock(password, Hash.HashAlgorithm.SHA3).Bytes);
aescipher.Init(true, aeskeyParameter);
outputBytes = new byte[aescipher.GetOutputSize(inputBytes.Bytes.Length)];
int aeslength = aescipher.ProcessBytes(inputBytes.Bytes, outputBytes, 0);
aescipher.DoFinal(outputBytes, aeslength);
Decryption
byte[] inputBytes = input.Bytes;
byte[] outputBytes = new byte[0];
AesEngine aesengine = new AesEngine();
CbcBlockCipher aesblockCipher = new CbcBlockCipher(aesengine);
PaddedBufferedBlockCipher aescipher = new PaddedBufferedBlockCipher(aesblockCipher);
KeyParameter aeskeyParameter = new KeyParameter(Hash.HashDataBlock(password, Hash.HashAlgorithm.SHA3).Bytes);
aescipher.Init(false, aeskeyParameter);
outputBytes = new byte[aescipher.GetOutputSize(inputBytes.Length)];
int aeslength = aescipher.ProcessBytes(inputBytes, outputBytes, 0);
aescipher.DoFinal(outputBytes, aeslength);
My Functions to save and load the keys. The DataBlock class just converts data to needed formats like UTF8, Base64 or just byte arrays:
public static void SaveKeyEncrypted(DataBlock key, string path, DataBlock password)
{
StreamWriter sw = new StreamWriter(path);
DataBlock encrypted = SymmetricEncryption.Encrypt(key, password, SymmetricEncryption.SymmetricAlgorithms.AES);
sw.Write(encrypted.Base64);
sw.Close();
}
public static DataBlock ReadKeyEncrypted(string path, DataBlock password)
{
StreamReader sr = new StreamReader(path);
DataBlock readData = new DataBlock(sr.ReadLine(), DataBlock.DataType.Base64);
sr.Close();
return SymmetricEncryption.Decrypt(readData, password, SymmetricEncryption.SymmetricAlgorithms.AES);
}
For reproduction my other code that has to do with this problem:
public class DataBlock
{
private byte[] _data;
public DataBlock()
{
this._data = new byte[0];
}
public enum DataType
{
UTF8,
UTF7,
UTF32,
ASCII,
Unicode,
Hex,
Base64,
Base32
}
public DataBlock(string data, DataType dataType) : this()
{
switch (dataType)
{
case DataType.UTF8:
this._data = Encoding.UTF8.GetBytes(data);
break;
case DataType.UTF7:
this._data = Encoding.UTF7.GetBytes(data);
break;
case DataType.UTF32:
this._data = Encoding.UTF32.GetBytes(data);
break;
case DataType.ASCII:
this._data = Encoding.ASCII.GetBytes(data);
break;
case DataType.Unicode:
this._data = Encoding.Unicode.GetBytes(data);
break;
case DataType.Hex:
this._data = new byte[data.Length / 2];
for (int i = 0; i < data.Length; i += 2)
{
this._data[i / 2] = Convert.ToByte(data.Substring(i, 2), 16);
}
break;
case DataType.Base64:
this._data = Convert.FromBase64String(data);
break;
case DataType.Base32:
this._data = this.FromBase32String(data);
break;
}
}
public DataBlock(byte[] data)
{
this._data = data;
}
public string UTF8
{
get
{
return Encoding.UTF8.GetString(this._data);
}
}
public string UTF7
{
get
{
return Encoding.UTF7.GetString(this._data);
}
}
public string UTF32
{
get
{
return Encoding.UTF32.GetString(this._data);
}
}
public string ASCII
{
get
{
return Encoding.ASCII.GetString(this._data);
}
}
public string Unicode
{
get
{
return Encoding.Unicode.GetString(this._data);
}
}
public string Hex
{
get
{
return BitConverter.ToString(this._data).Replace("-", "");
}
}
public string Base64
{
get
{
return Convert.ToBase64String(this._data);
}
}
public string Base32
{
get
{
return this.ToBase32String(this._data);
}
}
public byte[] Bytes
{
get
{
return this._data;
}
}
private string ValidChars = "QAZ2WSX3" + "EDC4RFV5" + "TGB6YHN7" + "UJM8K9LP";
private string ToBase32String(byte[] bytes)
{
StringBuilder sb = new StringBuilder();
byte index;
int hi = 5;
int currentByte = 0;
while (currentByte < bytes.Length)
{
if (hi > 8)
{
index = (byte)(bytes[currentByte++] >> (hi - 5));
if (currentByte != bytes.Length)
{
index = (byte)(((byte)(bytes[currentByte] << (16 - hi)) >> 3) | index);
}
hi -= 3;
}
else if (hi == 8)
{
index = (byte)(bytes[currentByte++] >> 3);
hi -= 3;
}
else
{
index = (byte)((byte)(bytes[currentByte] << (8 - hi)) >> 3);
hi += 5;
}
sb.Append(ValidChars[index]);
}
return sb.ToString();
}
public byte[] FromBase32String(string str)
{
int numBytes = str.Length * 5 / 8;
byte[] bytes = new Byte[numBytes];
str = str.ToUpper();
int bit_buffer;
int currentCharIndex;
int bits_in_buffer;
if (str.Length < 3)
{
bytes[0] = (byte)(ValidChars.IndexOf(str[0]) | ValidChars.IndexOf(str[1]) << 5);
return bytes;
}
bit_buffer = (ValidChars.IndexOf(str[0]) | ValidChars.IndexOf(str[1]) << 5);
bits_in_buffer = 10;
currentCharIndex = 2;
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = (byte)bit_buffer;
bit_buffer >>= 8;
bits_in_buffer -= 8;
while (bits_in_buffer < 8 && currentCharIndex < str.Length)
{
bit_buffer |= ValidChars.IndexOf(str[currentCharIndex++]) << bits_in_buffer;
bits_in_buffer += 5;
}
}
return bytes;
}
}
Function to generate a keypair
public static DataBlock[] GenerateKeyPair(KeyPairSize keyPairSize)
{
RsaKeyPairGenerator keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(new KeyGenerationParameters(new SecureRandom(new CryptoApiRandomGenerator()), (int) keyPairSize));
AsymmetricCipherKeyPair keyPair = keyPairGenerator.GenerateKeyPair();
PrivateKeyInfo pkInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public);
DataBlock[] keyPairData = new DataBlock[2];
keyPairData[0] = new DataBlock(pkInfo.GetDerEncoded());
keyPairData[1] = new DataBlock(info.GetDerEncoded());
return keyPairData;
}
Code to reproduce the error:
DataBlock[] keyPair = AsymmetricEncryption.GenerateKeyPair(AsymmetricEncryption.KeyPairSize.Bits512);
DataBlock pass = new DataBlock("1234", DataBlock.DataType.UTF8);
DataBlock orig = new DataBlock("Hello World", DataBlock.DataType.UTF8);
DataBlock encrypted = AsymmetricEncryption.Encrypt(orig, keyPair[1]);
AsymmetricEncryption.SaveKeyEncrypted(keyPair[0], "D:\\privateenc", pass);
AsymmetricEncryption.SaveKeyEncrypted(keyPair[1], "D:\\publicenc", pass);
DataBlock privateKey = AsymmetricEncryption.ReadKeyEncrypted("D:\\privateenc", pass);
DataBlock publicKey = AsymmetricEncryption.ReadKeyEncrypted("D:\\publicenc", pass);
DataBlock decrypted = AsymmetricEncryption.Decrypt(encrypted, privateKey);
Console.WriteLine(decrypted.UTF8);
The encryption/decryption method is not needed because the error already happens after reading the encrypted key on my harddrive.
Why/where is the extra data added and how can I fix it?
I was able to fix it by adding the initial byte array length of the key to the encrypted text and read it later on. In the read function I cut everything after the original size of the key.
The main problem is still present and this is just a workaround.
I have an object that has the following variables:
bool firstBool;
float firstFloat; (0.0 to 1.0)
float secondFloat (0.0 to 1.0)
int firstInt; (0 to 10,000)
I was using a ToString method to get a string that I can send over the network. Scaling up I have encountered issues with the amount of data this is taking up.
the string looks like this at the moment:
"false:1.0:1.0:10000" this is 19 characters at 2 bytes per so 38 bytes
I know that I can save on this size by manually storing the data in 4 bytes like this:
A|B|B|B|B|B|B|B
C|C|C|C|C|C|C|D
D|D|D|D|D|D|D|D
D|D|D|D|D|X|X|X
A = bool(0 or 1), B = int(0 to 128), C = int(0 to 128), D = int(0 to 16384), X = Leftover bits
I convert the float(0.0 to 1.0) to int(0 to 128) since I can rebuild them on the other end and the accuracy isn't super important.
I have been experimenting with BitArray and byte[] to convert the data into and out of the binary structure.
After some experiments I ended up with this serialization process(I know it needs to be cleaned up and optimized)
public byte[] Serialize() {
byte[] firstFloatBytes = BitConverter.GetBytes(Mathf.FloorToInt(firstFloat * 128)); //Convert the float to int from (0 to 128)
byte[] secondFloatBytes = BitConverter.GetBytes(Mathf.FloorToInt(secondFloat * 128)); //Convert the float to int from (0 to 128)
byte[] firstIntData = BitConverter.GetBytes(Mathf.FloorToInt(firstInt)); // Get the bytes for the int
BitArray data = new BitArray(32); // create the size 32 bitarray to hold all the data
int i = 0; // create the index value
data[i] = firstBool; // set the 0 bit
BitArray ffBits = new BitArray(firstFloatBytes);
for(i = 1; i < 8; i++) {
data[i] = ffBits[i-1]; // Set bits 1 to 7
}
BitArray sfBits = new BitArray(secondFloatBytes);
for(i = 8; i < 15; i++) {
data[i] = sfBits[i-8]; // Set bits 8 to 14
}
BitArray fiBits = new BitArray(firstIntData);
for(i = 15; i < 29; i++) {
data[i] = fiBits[i-15]; // Set bits 15 to 28
}
byte[] output = new byte[4]; // create a byte[] to hold the output
data.CopyTo(output,0); // Copy the bits to the byte[]
return output;
}
Getting the information back out of this structure is much more complicated than getting it into this form. I figure I can probably workout something using the bitwise operators and bitmasks.
This is proving to be more complicated than I was expecting. I thought it would be very easy to access the bits of a byte[] to manipulate the data directly, extract ranges of bits, then convert back to the values required to rebuild the object. Are there best practices for this type of data serialization? Does anyone know of a tutorial or example reference I could read?
Standard and efficient serialization methods are:
Using BinaryWriter / BinaryReader:
public byte[] Serialize()
{
using(var s = new MemoryStream())
using(var w = new BinaryWriter(s))
{
w.Write(firstBool);
w.Write(firstFloat);
...
return s.ToArray();
}
}
public void Deserialize(byte[] bytes)
{
using(var s = new MemoryStream(bytes))
using(var r = new BinaryReader(s))
{
firstBool = r.ReadBool();
firstFload = r.ReadFloat();
...
}
}
Using protobuf.net
BinaryWriter / BinaryReader is much faster (around 7 times). Protobuf is more flexible, easy to use, very popular and serializes into around 33% fewer bytes. (of course these numbers are orders of magnitude and depend on what you serialize and how).
Now basically BinaryWriter will write 1 + 4 + 4 + 4 = 13 bytes. You shrink it to 5 bytes by converting the values to bool, byte, byte, short first by rounding it the way you want. Finally it's easy to merge the bool with one of your bytes to get 4 bytes if you really want to.
I don't really discourage manual serialization. But it has to be worth the price in terms of performance. The code is quite unreadable. Use bit masks and binary shifts on bytes directly but keep it as simple as possible. Don't use BitArray. It's slow and not more readable.
Here is a simple method for pack/unpack. But you loose accuracy converting a float to only 7/8 bits
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
foreach (Data data in Data.input)
{
Data.Print(data);
Data results = Data.Unpack(Data.Pack(data));
Data.Print(results);
}
Console.ReadLine();
}
}
public class Data
{
public static List<Data> input = new List<Data>() {
new Data() { firstBool = true, firstFloat = 0.2345F, secondFloat = 0.432F, firstInt = 12},
new Data() { firstBool = true, firstFloat = 0.3445F, secondFloat = 0.432F, firstInt = 11},
new Data() { firstBool = false, firstFloat = 0.2365F, secondFloat = 0.432F, firstInt = 9},
new Data() { firstBool = false, firstFloat = 0.545F, secondFloat = 0.432F, firstInt = 8},
new Data() { firstBool = true, firstFloat = 0.2367F, secondFloat = 0.432F, firstInt = 7}
};
public bool firstBool { get; set; }
public float firstFloat {get; set; } //(0.0 to 1.0)
public float secondFloat {get; set; } //(0.0 to 1.0)
public int firstInt { get; set; } //(0 to 10,000)
public static byte[] Pack(Data data)
{
byte[] results = new byte[4];
results[0] = (byte)((data.firstBool ? 0x80 : 0x00) | (byte)(data.firstFloat * 128));
results[1] = (byte)(data.secondFloat * 256);
results[2] = (byte)((data.firstInt >> 8) & 0xFF);
results[3] = (byte)(data.firstInt & 0xFF);
return results;
}
public static Data Unpack(byte[] data)
{
Data results = new Data();
results.firstBool = ((data[0] & 0x80) == 0) ? false : true;
results.firstFloat = ((float)(data[0] & 0x7F)) / 128.0F;
results.secondFloat = (float)data[1] / 256.0F;
results.firstInt = (data[2] << 8) | data[3];
return results;
}
public static void Print(Data data)
{
Console.WriteLine("Bool : '{0}', 1st Float : '{1}', 2nd Float : '{2}', Int : '{3}'",
data.firstBool,
data.firstFloat,
data.secondFloat,
data.firstInt
);
}
}
}
In PHP
echo hash("crc32b","hello world");
//it print : 0d4a1185
Is there any equivalent method in c#?
bellow the class used in one of my c# projects :
class CRC32B
{
public static int Main(String INPUT)
{
// first convert string to byte-array
String input = INPUT;
byte[] bytes = new byte[input.Length * sizeof(char)];
System.Buffer.BlockCopy(input.ToCharArray(), 0, bytes, 0, bytes.Length);
// then calculate the value
int crcVal = (int)crc32(input);
if (crcVal < 0)
{
crcVal = crcVal * (-1);
}
//Console.WriteLine((int)crcVal);
return (int)crcVal;
}
public static uint crc32(string input)
{
var table = new uint[]{
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2,
0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,
0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106,
0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D,
0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7,
0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA,
0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84,
0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E,
0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55,
0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28,
0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,
0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69,
0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693,
0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
};
unchecked
{
uint crc = (uint)(((uint)0) ^ (1));
var len = input.Length;
for (var i = 0; i < len; i++)
{
crc = (crc >> 8) ^ table[
(crc ^ (byte)input[i]) & 0xFF
];
}
//crc = (uint)(crc ^ (-1));
if (crc < 0)
{
crc += (uint)4294967296;
crc = (uint)(crc ^ (-1));
}
return crc;
}
}
}
I want to use VerQueryValue in c# . But I dont know how can I use.
For example : This part wrote using c++ program(exe. or dll) Build date is embeded.So I have to get this build date
VS_VERSION_INFO VERSIONINFO
FILEVERSION BUILDVRS1,BUILDVRS2,BUILDVRS3,BUILDVRS4
PRODUCTVERSION BUILDVRS1,BUILDVRS2,BUILDVRS3,BUILDVRS0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x4L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "LastModified" , "20170313\0"
VALUE "Comments", "\0"
VALUE "CompanyName", BUILDCOMPANY
VALUE "FileVersion", BUILDFILE
VALUE "LegalCopyright", "Copyright (C) 1999-2002\0"
VALUE "LegalTrademarks", "\0"
VALUE "ProductVersion", BUILDPROD
VALUE "InternalName", "SeriCom\0"
VALUE "OriginalFilename", "SeriCom.DLL\0"
VALUE "FileDescription", "SeriCom DLL\0"
VALUE "ProductName", "SeriCom Dynamic Link Library\0"
VALUE "BuildMach", BUILDMACH
VALUE "BuildDate", BUILDDATE
VALUE "BuildType", BUILDTYPE
VALUE "BuildVers", BUILDVPRX
VALUE "BuildNumb", BUILDNUMB
VALUE "BuildChar", BUILDCHAR
VALUE "BuildEnv1", BUILDCOMP
VALUE "BuildEnv2", BUILDMFC
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
I want to write c# code for get build date in exe or dll file.
You can use some pinvokes...
[DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetFileVersionInfoSize(string lptstrFilename, out int lpdwHandle);
[DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetFileVersionInfo(string lptstrFilename, int dwHandle, int dwLen, byte[] lpData);
[DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool VerQueryValue(byte[] pBlock, string lpSubBlock, out IntPtr lplpBuffer, out int puLen);
public static Tuple<string, string>[] GetVersionInfo(string fileName, params string[] keys)
{
int num;
int size = GetFileVersionInfoSize(fileName, out num);
if (size == 0)
{
throw new Win32Exception();
}
var bytes = new byte[size];
bool success = GetFileVersionInfo(fileName, 0, size, bytes);
if (!success)
{
throw new Win32Exception();
}
int size2;
IntPtr ptr;
success = VerQueryValue(bytes, #"\VarFileInfo\Translation", out ptr, out size2);
uint[] langs;
if (success)
{
langs = new uint[size2 / 4];
for (int i = 0, j = 0; j < size2; i++, j += 4)
{
langs[i] = unchecked((uint)(((ushort)Marshal.ReadInt16(ptr, j) << 16) | (ushort)Marshal.ReadInt16(ptr, j + 2)));
}
}
else
{
// Taken from https://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/FileVersionInfo.cs,470
langs = new uint[] { 0x040904B0, 0x040904E4, 0x04090000 };
}
string[] langs2 = Array.ConvertAll(langs, x => #"\StringFileInfo\" + x.ToString("X8") + #"\");
var kv = new Tuple<string, string>[keys.Length];
for (int i = 0; i < kv.Length; i++)
{
string key = keys[i];
string value = null;
foreach (var lang in langs2)
{
success = VerQueryValue(bytes, lang + key, out ptr, out size2);
if (success)
{
value = Marshal.PtrToStringUni(ptr);
break;
}
}
kv[i] = Tuple.Create(key, value);
}
return kv;
}
and then you use:
string name = "Win32Project1.exe";
var infos = GetVersionInfo(name, "LastModified", "Comments", "CompanyName", "FileVersion", "LegalCopyright", "LegalTrademarks", "ProductVersion", "InternalName", "OriginalFilename", "FileDescription", "ProductName", "BuildDate");
var buildDate = infos.Single(x => x.Item1 == "BuildDate").Item2;
Out of curiosity I've began exploring the various structs of the VS_VERSIONINFO, and I've written some code:
public class VersionInfo
{
[DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetFileVersionInfoSize(string lptstrFilename, out int lpdwHandle);
[DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetFileVersionInfo(string lptstrFilename, int dwHandle, int dwLen, byte[] lpData);
[DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool VerQueryValue(byte[] pBlock, string lpSubBlock, out IntPtr lplpBuffer, out int puLen);
public readonly Version FileVersion;
public readonly Version ProductVersion;
public readonly uint FileFlagsMask;
public readonly uint FileFlags;
public readonly uint FileOS;
public readonly uint FileType;
public readonly uint FileSubtype;
// Always null
public readonly DateTime? FileDate;
protected VersionInfo(Version fileVersion, Version productVersion, uint fileFlagsMask, uint fileFlags, uint fileOS, uint fileType, uint fileSubtype, DateTime? fileDate)
{
FileVersion = fileVersion;
ProductVersion = productVersion;
FileFlagsMask = fileFlagsMask;
FileFlags = fileFlags;
FileOS = fileOS;
FileType = fileType;
FileSubtype = fileSubtype;
FileDate = fileDate;
}
// vi can be null on exit
// Item1 = language | codepage
// Item2 = Key
// Item3 = Value
public static IEnumerable<Tuple<uint, string, string>> ReadVersionInfo(string fileName, out VersionInfo vi)
{
int num;
int size = GetFileVersionInfoSize(fileName, out num);
if (size == 0)
{
throw new Win32Exception();
}
var buffer = new byte[size];
bool success = GetFileVersionInfo(fileName, 0, size, buffer);
if (!success)
{
throw new Win32Exception();
}
return ReadVersionInfo(buffer, out vi);
}
// vi can be null on exit
// Item1 = language | codepage
// Item2 = Key
// Item3 = Value
public static IEnumerable<Tuple<uint, string, string>> ReadVersionInfo(byte[] buffer, out VersionInfo vi)
{
int offset;
// The offset calculated here is unused
var fibs = ReadFileInfoBaseStruct(buffer, 0, out offset);
if (fibs.Key != "VS_VERSION_INFO")
{
throw new Exception(fibs.Key);
}
// Value = VS_FIXEDFILEINFO
if (fibs.ValueLength != 0)
{
uint signature = BitConverter.ToUInt32(buffer, fibs.ValueOffset);
if (signature != 0xFEEF04BD)
{
throw new Exception(signature.ToString("X8"));
}
uint strucVersion = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 4);
var fileVersion = new Version(BitConverter.ToUInt16(buffer, fibs.ValueOffset + 10), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 8), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 14), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 12));
var productVersion = new Version(BitConverter.ToUInt16(buffer, fibs.ValueOffset + 18), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 16), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 22), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 20));
uint fileFlagsMask = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 24);
uint fileFlags = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 28);
uint fileOS = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 32);
uint fileType = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 36);
uint fileSubtype = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 40);
uint fileDateMS = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 44);
uint fileDateLS = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 48);
DateTime? fileDate = fileDateMS != 0 || fileDateLS != 0 ?
(DateTime?)DateTime.FromFileTime((long)fileDateMS << 32 | fileDateLS) :
null;
vi = new VersionInfo(fileVersion, productVersion, fileFlagsMask, fileFlags, fileOS, fileType, fileSubtype, fileDate);
}
else
{
vi = null;
}
return ReadVersionInfoInternal(buffer, fibs);
}
protected static IEnumerable<Tuple<uint, string, string>> ReadVersionInfoInternal(byte[] buffer, FileInfoBaseStruct fibs)
{
int sfiOrValOffset = (fibs.ValueOffset + fibs.ValueLength + 3) & (~3);
while (sfiOrValOffset < fibs.Length)
{
int nextSfiOrValOffset;
var sfiOrVal = ReadFileInfoBaseStruct(buffer, sfiOrValOffset, out nextSfiOrValOffset);
if (sfiOrVal.Key == "StringFileInfo")
{
int stOffset = sfiOrVal.ValueOffset;
while (stOffset < sfiOrVal.EndOffset)
{
int nextStOffset;
var st = ReadFileInfoBaseStruct(buffer, stOffset, out nextStOffset);
uint langCharset = uint.Parse(st.Key, NumberStyles.HexNumber);
int striOffset = st.ValueOffset;
while (striOffset < st.EndOffset)
{
int nextStriOffset;
var stri = ReadFileInfoBaseStruct(buffer, striOffset, out nextStriOffset);
// Here stri.ValueLength is in words!
int len = FindLengthUnicodeSZ(buffer, stri.ValueOffset, stri.ValueOffset + (stri.ValueLength * 2));
string value = Encoding.Unicode.GetString(buffer, stri.ValueOffset, len * 2);
yield return Tuple.Create(langCharset, stri.Key, value);
striOffset = nextStriOffset;
}
stOffset = nextStOffset;
}
}
else if (sfiOrVal.Key == "VarFileInfo")
{
int varOffset = sfiOrVal.ValueOffset;
while (varOffset < sfiOrVal.EndOffset)
{
int nextVarOffset;
var var = ReadFileInfoBaseStruct(buffer, varOffset, out nextVarOffset);
if (var.Key != "Translation")
{
throw new Exception(var.Key);
}
int langOffset = var.ValueOffset;
while (langOffset < var.EndOffset)
{
unchecked
{
// We invert the order suggested by the Var description!
uint high = (uint)BitConverter.ToInt16(buffer, langOffset);
uint low = (uint)BitConverter.ToInt16(buffer, langOffset + 2);
uint lang = (high << 16) | low;
langOffset += 4;
}
}
varOffset = nextVarOffset;
}
}
else
{
Debug.WriteLine("Unrecognized " + sfiOrVal.Key);
}
sfiOrValOffset = nextSfiOrValOffset;
}
}
protected static FileInfoBaseStruct ReadFileInfoBaseStruct(byte[] buffer, int offset, out int nextOffset)
{
var fibs = new FileInfoBaseStruct
{
Length = BitConverter.ToInt16(buffer, offset),
ValueLength = BitConverter.ToInt16(buffer, offset + 2),
Type = BitConverter.ToInt16(buffer, offset + 4)
};
int len = FindLengthUnicodeSZ(buffer, offset + 6, offset + fibs.Length);
fibs.Key = Encoding.Unicode.GetString(buffer, offset + 6, len * 2);
// Padding
fibs.ValueOffset = ((offset + 6 + (len + 1) * 2) + 3) & (~3);
fibs.EndOffset = offset + fibs.Length;
nextOffset = (fibs.EndOffset + 3) & (~3);
return fibs;
}
protected static int FindLengthUnicodeSZ(byte[] buffer, int offset, int endOffset)
{
int offset2 = offset;
while (offset2 < endOffset && BitConverter.ToInt16(buffer, offset2) != 0)
{
offset2 += 2;
}
// In chars
return (offset2 - offset) / 2;
}
// Used internally
protected class FileInfoBaseStruct
{
public short Length { get; set; }
public short ValueLength { get; set; }
public short Type { get; set; }
public string Key { get; set; }
public int ValueOffset { get; set; }
public int EndOffset { get; set; }
}
}
use it like:
string name = "Win32Project1-loc.exe";
// vi could be null on return from ReadVersionInfo
VersionInfo vi;
// Note that it is an IEnumerable<>... If you want to use
// it multipel times, you should .ToArray() it!
var infos = VersionInfo.ReadVersionInfo(name, out vi);
// For example
var buildDate = infos.Single(x => x.Item2 == "BuildDate").Item3;
Take reference of this queston
public static DateTime GetLinkerTime(this Assembly assembly, TimeZoneInfo target = null)
{
var filePath = assembly.Location;
const int c_PeHeaderOffset = 60;
const int c_LinkerTimestampOffset = 8;
var buffer = new byte[2048];
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
stream.Read(buffer, 0, 2048);
var offset = BitConverter.ToInt32(buffer, c_PeHeaderOffset);
var secondsSince1970 = BitConverter.ToInt32(buffer, offset + c_LinkerTimestampOffset);
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var linkTimeUtc = epoch.AddSeconds(secondsSince1970);
var tz = target ?? TimeZoneInfo.Local;
var localTime = TimeZoneInfo.ConvertTimeFromUtc(linkTimeUtc, tz);
return localTime;
}
Usage example:
var linkTimeLocal = Assembly.GetExecutingAssembly().GetLinkerTime();
How do I get a human-readable file size in bytes abbreviation using .NET?
Example:
Take input 7,326,629 and display 6.98 MB
This may not the most efficient or optimized way to do it, but it's easier to read if you are not familiar with log maths, and should be fast enough for most scenarios.
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
double len = new FileInfo(filename).Length;
int order = 0;
while (len >= 1024 && order < sizes.Length - 1) {
order++;
len = len/1024;
}
// Adjust the format string to your preferences. For example "{0:0.#}{1}" would
// show a single decimal place, and no space.
string result = String.Format("{0:0.##} {1}", len, sizes[order]);
using Log to solve the problem....
static String BytesToString(long byteCount)
{
string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; //Longs run out around EB
if (byteCount == 0)
return "0" + suf[0];
long bytes = Math.Abs(byteCount);
int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
double num = Math.Round(bytes / Math.Pow(1024, place), 1);
return (Math.Sign(byteCount) * num).ToString() + suf[place];
}
Also in C#, but should be a snap to convert. Also I rounded to 1 decimal place for readability.
Basically determine the number of decimal places in Base 1024 and then divide by 1024^decimalplaces.
And some samples of use and output:
Console.WriteLine(BytesToString(9223372036854775807)); //Results in 8EB
Console.WriteLine(BytesToString(0)); //Results in 0B
Console.WriteLine(BytesToString(1024)); //Results in 1KB
Console.WriteLine(BytesToString(2000000)); //Results in 1.9MB
Console.WriteLine(BytesToString(-9023372036854775807)); //Results in -7.8EB
Edit:
Was pointed out that I missed a Math.Floor, so I incorporated it. (Convert.ToInt32 uses rounding, not truncating and that's why Floor is necessary.) Thanks for the catch.
Edit2:
There were a couple of comments about negative sizes and 0 byte sizes, so I updated to handle those cases.
A tested and significantly optimized version of the requested function is posted here:
C# Human Readable File Size - Optimized Function
Source code:
// Returns the human-readable file size for an arbitrary, 64-bit file size
// The default format is "0.### XB", e.g. "4.2 KB" or "1.434 GB"
public string GetBytesReadable(long i)
{
// Get absolute value
long absolute_i = (i < 0 ? -i : i);
// Determine the suffix and readable value
string suffix;
double readable;
if (absolute_i >= 0x1000000000000000) // Exabyte
{
suffix = "EB";
readable = (i >> 50);
}
else if (absolute_i >= 0x4000000000000) // Petabyte
{
suffix = "PB";
readable = (i >> 40);
}
else if (absolute_i >= 0x10000000000) // Terabyte
{
suffix = "TB";
readable = (i >> 30);
}
else if (absolute_i >= 0x40000000) // Gigabyte
{
suffix = "GB";
readable = (i >> 20);
}
else if (absolute_i >= 0x100000) // Megabyte
{
suffix = "MB";
readable = (i >> 10);
}
else if (absolute_i >= 0x400) // Kilobyte
{
suffix = "KB";
readable = i;
}
else
{
return i.ToString("0 B"); // Byte
}
// Divide by 1024 to get fractional value
readable = (readable / 1024);
// Return formatted number with suffix
return readable.ToString("0.### ") + suffix;
}
[DllImport ( "Shlwapi.dll", CharSet = CharSet.Auto )]
public static extern long StrFormatByteSize (
long fileSize
, [MarshalAs ( UnmanagedType.LPTStr )] StringBuilder buffer
, int bufferSize );
/// <summary>
/// Converts a numeric value into a string that represents the number expressed as a size value in bytes, kilobytes, megabytes, or gigabytes, depending on the size.
/// </summary>
/// <param name="filelength">The numeric value to be converted.</param>
/// <returns>the converted string</returns>
public static string StrFormatByteSize (long filesize) {
StringBuilder sb = new StringBuilder( 11 );
StrFormatByteSize( filesize, sb, sb.Capacity );
return sb.ToString();
}
From: http://www.pinvoke.net/default.aspx/shlwapi/StrFormatByteSize.html
Check out my ByteSize library. It's the System.TimeSpan for bytes!
It handles the conversion and formatting for you.
var maxFileSize = ByteSize.FromKiloBytes(10);
maxFileSize.Bytes;
maxFileSize.MegaBytes;
maxFileSize.GigaBytes;
It also does string representation and parsing.
// ToString
ByteSize.FromKiloBytes(1024).ToString(); // 1 MB
ByteSize.FromGigabytes(.5).ToString(); // 512 MB
ByteSize.FromGigabytes(1024).ToString(); // 1 TB
// Parsing
ByteSize.Parse("5b");
ByteSize.Parse("1.55B");
One more way to skin it, without any kind of loops and with negative size support (makes sense for things like file size deltas):
public static class Format
{
static string[] sizeSuffixes = {
"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
public static string ByteSize(long size)
{
Debug.Assert(sizeSuffixes.Length > 0);
const string formatTemplate = "{0}{1:0.#} {2}";
if (size == 0)
{
return string.Format(formatTemplate, null, 0, sizeSuffixes[0]);
}
var absSize = Math.Abs((double)size);
var fpPower = Math.Log(absSize, 1000);
var intPower = (int)fpPower;
var iUnit = intPower >= sizeSuffixes.Length
? sizeSuffixes.Length - 1
: intPower;
var normSize = absSize / Math.Pow(1000, iUnit);
return string.Format(
formatTemplate,
size < 0 ? "-" : null, normSize, sizeSuffixes[iUnit]);
}
}
And here is the test suite:
[TestFixture] public class ByteSize
{
[TestCase(0, Result="0 B")]
[TestCase(1, Result = "1 B")]
[TestCase(1000, Result = "1 KB")]
[TestCase(1500000, Result = "1.5 MB")]
[TestCase(-1000, Result = "-1 KB")]
[TestCase(int.MaxValue, Result = "2.1 GB")]
[TestCase(int.MinValue, Result = "-2.1 GB")]
[TestCase(long.MaxValue, Result = "9.2 EB")]
[TestCase(long.MinValue, Result = "-9.2 EB")]
public string Format_byte_size(long size)
{
return Format.ByteSize(size);
}
}
I like to use the following method (it supports up to terabytes, which is enough for most cases, but it can easily be extended):
private string GetSizeString(long length)
{
long B = 0, KB = 1024, MB = KB * 1024, GB = MB * 1024, TB = GB * 1024;
double size = length;
string suffix = nameof(B);
if (length >= TB) {
size = Math.Round((double)length / TB, 2);
suffix = nameof(TB);
}
else if (length >= GB) {
size = Math.Round((double)length / GB, 2);
suffix = nameof(GB);
}
else if (length >= MB) {
size = Math.Round((double)length / MB, 2);
suffix = nameof(MB);
}
else if (length >= KB) {
size = Math.Round((double)length / KB, 2);
suffix = nameof(KB);
}
return $"{size} {suffix}";
}
Please keep in mind that this is written for C# 6.0 (2015), so it might need a little editing for earlier versions.
int size = new FileInfo( filePath ).Length / 1024;
string humanKBSize = string.Format( "{0} KB", size );
string humanMBSize = string.Format( "{0} MB", size / 1024 );
string humanGBSize = string.Format( "{0} GB", size / 1024 / 1024 );
Here's a concise answer that determines the unit automatically.
public static string ToBytesCount(this long bytes)
{
int unit = 1024;
string unitStr = "B";
if (bytes < unit)
{
return string.Format("{0} {1}", bytes, unitStr);
}
int exp = (int)(Math.Log(bytes) / Math.Log(unit));
return string.Format("{0:##.##} {1}{2}", bytes / Math.Pow(unit, exp), "KMGTPEZY"[exp - 1], unitStr);
}
"b" is for bit, "B" is for Byte and "KMGTPEZY" are respectively for kilo, mega, giga, tera, peta, exa, zetta and yotta
One can expand it to take ISO/IEC80000 into account:
public static string ToBytesCount(this long bytes, bool isISO = true)
{
int unit = isISO ? 1024 : 1000;
string unitStr = "B";
if (bytes < unit)
{
return string.Format("{0} {1}", bytes, unitStr);
}
int exp = (int)(Math.Log(bytes) / Math.Log(unit));
return string.Format("{0:##.##} {1}{2}{3}", bytes / Math.Pow(unit, exp), "KMGTPEZY"[exp - 1], isISO ? "i" : "", unitStr);
}
string[] suffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
int s = 0;
long size = fileInfo.Length;
while (size >= 1024)
{
s++;
size /= 1024;
}
string humanReadable = String.Format("{0} {1}", size, suffixes[s]);
There is one open source project which can do that and much more.
7.Bits().ToString(); // 7 b
8.Bits().ToString(); // 1 B
(.5).Kilobytes().Humanize(); // 512 B
(1000).Kilobytes().ToString(); // 1000 KB
(1024).Kilobytes().Humanize(); // 1 MB
(.5).Gigabytes().Humanize(); // 512 MB
(1024).Gigabytes().ToString(); // 1 TB
http://humanizr.net/#bytesize
https://github.com/MehdiK/Humanizer
If you are trying to match the size as shown in Windows Explorer's detail view, this is the code you want:
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
private static extern long StrFormatKBSize(
long qdw,
[MarshalAs(UnmanagedType.LPTStr)] StringBuilder pszBuf,
int cchBuf);
public static string BytesToString(long byteCount)
{
var sb = new StringBuilder(32);
StrFormatKBSize(byteCount, sb, sb.Capacity);
return sb.ToString();
}
This will not only match Explorer exactly but will also provide the strings translated for you and match differences in Windows versions (for example in Win10, K = 1000 vs. previous versions K = 1024).
Mixture of all solutions :-)
/// <summary>
/// Converts a numeric value into a string that represents the number expressed as a size value in bytes,
/// kilobytes, megabytes, or gigabytes, depending on the size.
/// </summary>
/// <param name="fileSize">The numeric value to be converted.</param>
/// <returns>The converted string.</returns>
public static string FormatByteSize(double fileSize)
{
FileSizeUnit unit = FileSizeUnit.B;
while (fileSize >= 1024 && unit < FileSizeUnit.YB)
{
fileSize = fileSize / 1024;
unit++;
}
return string.Format("{0:0.##} {1}", fileSize, unit);
}
/// <summary>
/// Converts a numeric value into a string that represents the number expressed as a size value in bytes,
/// kilobytes, megabytes, or gigabytes, depending on the size.
/// </summary>
/// <param name="fileInfo"></param>
/// <returns>The converted string.</returns>
public static string FormatByteSize(FileInfo fileInfo)
{
return FormatByteSize(fileInfo.Length);
}
}
public enum FileSizeUnit : byte
{
B,
KB,
MB,
GB,
TB,
PB,
EB,
ZB,
YB
}
Like #NET3's solution. Use shift instead of division to test the range of bytes, because division takes more CPU cost.
private static readonly string[] UNITS = new string[] { "B", "KB", "MB", "GB", "TB", "PB", "EB" };
public static string FormatSize(ulong bytes)
{
int c = 0;
for (c = 0; c < UNITS.Length; c++)
{
ulong m = (ulong)1 << ((c + 1) * 10);
if (bytes < m)
break;
}
double n = bytes / (double)((ulong)1 << (c * 10));
return string.Format("{0:0.##} {1}", n, UNITS[c]);
}
I use the Long extension method below to convert to a human readable size string. This method is the C# implementation of the Java solution of this same question posted on Stack Overflow, here.
/// <summary>
/// Convert a byte count into a human readable size string.
/// </summary>
/// <param name="bytes">The byte count.</param>
/// <param name="si">Whether or not to use SI units.</param>
/// <returns>A human readable size string.</returns>
public static string ToHumanReadableByteCount(
this long bytes
, bool si
)
{
var unit = si
? 1000
: 1024;
if (bytes < unit)
{
return $"{bytes} B";
}
var exp = (int) (Math.Log(bytes) / Math.Log(unit));
return $"{bytes / Math.Pow(unit, exp):F2} " +
$"{(si ? "kMGTPE" : "KMGTPE")[exp - 1] + (si ? string.Empty : "i")}B";
}
I assume you're looking for "1.4 MB" instead of "1468006 bytes"?
I don't think there is a built-in way to do that in .NET. You'll need to just figure out which unit is appropriate, and format it.
Edit: Here's some sample code to do just that:
http://www.codeproject.com/KB/cpp/formatsize.aspx
One more approach, for what it's worth. I liked #humbads optimized solution referenced above, so have copied the principle, but I've implemented it a little differently.
I suppose it's debatable as to whether it should be an extension method (since not all longs are necessarily byte sizes), but I like them, and it's somewhere I can find the method when I next need it!
Regarding the units, I don't think I've ever said 'Kibibyte' or 'Mebibyte' in my life, and while I'm skeptical of such enforced rather than evolved standards, I suppose it'll avoid confusion in the long term.
public static class LongExtensions
{
private static readonly long[] numberOfBytesInUnit;
private static readonly Func<long, string>[] bytesToUnitConverters;
static LongExtensions()
{
numberOfBytesInUnit = new long[6]
{
1L << 10, // Bytes in a Kibibyte
1L << 20, // Bytes in a Mebibyte
1L << 30, // Bytes in a Gibibyte
1L << 40, // Bytes in a Tebibyte
1L << 50, // Bytes in a Pebibyte
1L << 60 // Bytes in a Exbibyte
};
// Shift the long (integer) down to 1024 times its number of units, convert to a double (real number),
// then divide to get the final number of units (units will be in the range 1 to 1023.999)
Func<long, int, string> FormatAsProportionOfUnit = (bytes, shift) => (((double)(bytes >> shift)) / 1024).ToString("0.###");
bytesToUnitConverters = new Func<long,string>[7]
{
bytes => bytes.ToString() + " B",
bytes => FormatAsProportionOfUnit(bytes, 0) + " KiB",
bytes => FormatAsProportionOfUnit(bytes, 10) + " MiB",
bytes => FormatAsProportionOfUnit(bytes, 20) + " GiB",
bytes => FormatAsProportionOfUnit(bytes, 30) + " TiB",
bytes => FormatAsProportionOfUnit(bytes, 40) + " PiB",
bytes => FormatAsProportionOfUnit(bytes, 50) + " EiB",
};
}
public static string ToReadableByteSizeString(this long bytes)
{
if (bytes < 0)
return "-" + Math.Abs(bytes).ToReadableByteSizeString();
int counter = 0;
while (counter < numberOfBytesInUnit.Length)
{
if (bytes < numberOfBytesInUnit[counter])
return bytesToUnitConverters[counter](bytes);
counter++;
}
return bytesToUnitConverters[counter](bytes);
}
}
How about some recursion:
private static string ReturnSize(double size, string sizeLabel)
{
if (size > 1024)
{
if (sizeLabel.Length == 0)
return ReturnSize(size / 1024, "KB");
else if (sizeLabel == "KB")
return ReturnSize(size / 1024, "MB");
else if (sizeLabel == "MB")
return ReturnSize(size / 1024, "GB");
else if (sizeLabel == "GB")
return ReturnSize(size / 1024, "TB");
else
return ReturnSize(size / 1024, "PB");
}
else
{
if (sizeLabel.Length > 0)
return string.Concat(size.ToString("0.00"), sizeLabel);
else
return string.Concat(size.ToString("0.00"), "Bytes");
}
}
Then you call it:
return ReturnSize(size, string.Empty);
In order to get the human-readable string exactly as the user's used to in his Windows environment, you should use StrFormatByteSize():
using System.Runtime.InteropServices;
...
private long mFileSize;
[DllImport("Shlwapi.dll", CharSet = CharSet.Auto)]
public static extern int StrFormatByteSize(
long fileSize,
[MarshalAs(UnmanagedType.LPTStr)] StringBuilder buffer,
int bufferSize);
public string HumanReadableFileSize
{
get
{
var sb = new StringBuilder(20);
StrFormatByteSize(mFileSize, sb, 20);
return sb.ToString();
}
}
I found this here:
http://csharphelper.com/blog/2014/07/format-file-sizes-in-kb-mb-gb-and-so-forth-in-c/
My 2 cents:
The prefix for kilobyte is kB (lowercase K)
Since these functions are for presentation purposes, one should supply a culture, for example: string.Format(CultureInfo.CurrentCulture, "{0:0.##} {1}", fileSize, unit);
Depending on the context a kilobyte can be either 1000 or 1024 bytes. The same goes for MB, GB, etc.
Here is a method with Log10:
using System;
class Program {
static string NumberFormat(double n) {
var n2 = (int)Math.Log10(n) / 3;
var n3 = n / Math.Pow(1e3, n2);
return String.Format("{0:f3}", n3) + new[]{"", " k", " M", " G"}[n2];
}
static void Main() {
var s = NumberFormat(9012345678);
Console.WriteLine(s == "9.012 G");
}
}
https://learn.microsoft.com/dotnet/api/system.math.log10
Here is a BigInteger version of #deepee1's answer that gets around the size limitation of longs (so therefore supports yottabyte and theoretically whatever comes after that):
public static string ToBytesString(this BigInteger byteCount, string format = "N3")
{
string[] suf = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "YiB" };
if (byteCount.IsZero)
{
return $"{0.0.ToString(format)} {suf[0]}";
}
var abs = BigInteger.Abs(byteCount);
var place = Convert.ToInt32(Math.Floor(BigInteger.Log(abs, 1024)));
var pow = Math.Pow(1024, place);
// since we need to do this with integer math, get the quotient and remainder
var quotient = BigInteger.DivRem(abs, new BigInteger(pow), out var remainder);
// convert the remainder to a ratio and add both back together as doubles
var num = byteCount.Sign * (Math.Floor((double)quotient) + ((double)remainder / pow));
return $"{num.ToString(format)} {suf[place]}";
}
I made up this and it works just fine.
public string[] DetermineDigitalSize(string filename)
{
string[] result = new string[2];
string[] sizes = { "B", "KB", "MB", "GB", "GB" };
double len = new FileInfo(filename).Length;
double adjustedSize = len;
double testSize = 0;
int order = 0;
while (order< sizes.Length-1)
{
testSize = adjustedSize / 1024;
if (testSize >= 1) { adjustedSize = testSize; order++; }
else { break; }
}
result[0] = $"{adjustedSize:f2}";
result[1] = sizes[order];
return result;
}
Nothing here did exactly what I needed, and I made my own based on this thread, so here's my long extension that allows you to choose the formatting requirement depending on standard.
Definitely not the fastest but flexible. Supports up to EB/EiB.
// <summary>
/// <paramref name="byteCount"/> The original size in bytes ( 8 bits )
/// <paramref name="notationFormat"/> is supported in the following ways:
/// [ 'B' / 'b' : Binary : Kilobyte (KB) is 1024 bytes, Megabyte (MB) is 1048576 bytes, etc ]
/// [ 'I' / 'i' : IEC: Kibibyte (KiB) is 1024 bytes, Mebibyte (MiB) is 1048576 bytes, etc ]
/// [ 'D' / 'd' : Decimal : Kilobyte (KB) is 1000 bytes, Megabyte (MB) is 1000000 bytes, etc ]
/// </summary>
public static string ToDataSizeString( this long byteCount, char notationFormat = 'b' )
{
char[] supportedFormatChars = { 'b', 'i', 'd' };
var lowerCaseNotationFormat = char.ToLowerInvariant( notationFormat );
// Stop shooting holes in my ship!
if ( !supportedFormatChars.Contains( lowerCaseNotationFormat ) )
{
throw new ArgumentException( $"notationFormat argument '{notationFormat}' not supported" );
}
long ebLimit = 1152921504606846976;
long pbLimit = 1125899906842624;
long tbLimit = 1099511627776;
long gbLimit = 1073741824;
long mbLimit = 1048576;
long kbLimit = 1024;
var ebSuffix = "EB";
var pbSuffix = "PB";
var tbSuffix = "TB";
var gbSuffix = "GB";
var mbSuffix = "MB";
var kbSuffix = "KB";
var bSuffix = " B";
switch ( lowerCaseNotationFormat )
{
case 'b':
// Sweet as
break;
case 'i':
// Limits stay the same, suffixes need changed
ebSuffix = "EiB";
pbSuffix = "PiB";
tbSuffix = "TiB";
gbSuffix = "GiB";
mbSuffix = "MiB";
kbSuffix = "KiB";
bSuffix = " B";
break;
case 'd':
// Suffixes stay the same, limits need changed
ebLimit = 1000000000000000000;
pbLimit = 1000000000000000;
tbLimit = 1000000000000;
gbLimit = 1000000000;
mbLimit = 1000000;
kbLimit = 1000;
break;
default:
// Should have already Excepted, but hey whatever
throw new ArgumentException( $"notationFormat argument '{notationFormat}' not supported" );
}
string fileSizeText;
// Exa/Exbi sized
if ( byteCount >= ebLimit )
{
fileSizeText = $"{( (double)byteCount / ebLimit ):N1} {ebSuffix}";
}
// Peta/Pebi sized
else if ( byteCount >= pbLimit )
{
fileSizeText = $"{( (double)byteCount / pbLimit ):N1} {pbSuffix}";
}
// Tera/Tebi sized
else if ( byteCount >= tbLimit )
{
fileSizeText = $"{( (double)byteCount / tbLimit ):N1} {tbSuffix}";
}
// Giga/Gibi sized
else if ( byteCount >= gbLimit )
{
fileSizeText = $"{( (double)byteCount / gbLimit ):N1} {gbSuffix}";
}
// Mega/Mibi sized
else if ( byteCount >= mbLimit )
{
fileSizeText = $"{( (double)byteCount / mbLimit ):N1} {mbSuffix}";
}
// Kilo/Kibi sized
else if ( byteCount >= kbLimit )
{
fileSizeText = $"{( (double)byteCount / kbLimit ):N1} {kbSuffix}";
}
// Byte sized
else
{
fileSizeText = $"{byteCount} {bSuffix}";
}
return fileSizeText;
}
1-liner (plus the prefixes constant)
const String prefixes = " KMGTPEY";
/// <summary> Returns the human-readable file size for an arbitrary, 64-bit file size. </summary>
public static String HumanSize(UInt64 bytes)
=> Enumerable
.Range(0, prefixes.Length)
.Where(i => bytes < 1024U<<(i*10))
.Select(i => $"{(bytes>>(10*i-10))/1024:0.###} {prefixes[i]}B")
.First();
Or, if you want to reduce LINQ object allocations, use for-loop variation of the same:
/// <summary>
/// Returns the human-readable file size for an arbitrary, 64-bit file size.
/// </summary>
public static String HumanSize(UInt64 bytes)
{
const String prefixes = " KMGTPEY";
for (var i = 0; i < prefixes.Length; i++)
if (bytes < 1024U<<(i*10))
return $"{(bytes>>(10*i-10))/1024:0.###} {prefixes[i]}B";
throw new ArgumentOutOfRangeException(nameof(bytes));
}
This question is old, but a very fast C# function could be:
public static string PrettyPrintBytes(long numBytes)
{
if (numBytes < 1024)
return $"{numBytes} B";
if (numBytes < 1048576)
return $"{numBytes / 1024d:0.##} KB";
if (numBytes < 1073741824)
return $"{numBytes / 1048576d:0.##} MB";
if (numBytes < 1099511627776)
return $"{numBytes / 1073741824d:0.##} GB";
if (numBytes < 1125899906842624)
return $"{numBytes / 1099511627776d:0.##} TB";
if (numBytes < 1152921504606846976)
return $"{numBytes / 1125899906842624d:0.##} PB";
return $"{numBytes / 1152921504606846976d:0.##} EB";
}
This has only one cast and one divide per call and only up to 6 compares. When benchmarking, I found that string interpolation is much faster than using String.Format().