USD Wiper with DoD 5220.22-M - IsBadWritePtr - 04-27-2019
I needed a USB wiper and I coded one really fast, if someone is needs one, here is the code. Its not perfect but makes the job right
Code: #include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <windows.h>
#include <winioctl.h>
#include <ntstatus.h>
#define SIZE_OF_GB (SIZE_OF_MB * 1024)
#define SIZE_OF_MB (SIZE_OF_KB * 1024)
#define SIZE_OF_KB (1 * 1024)
typedef double DOUBLE, *PDOUBLE;
typedef long double LDOUBLE, *PLDOUBLE;
typedef struct _DEVICE {
HANDLE Handle;
PWSTR szProductId;
ULONG dwBytesPerSector;
ULONG64 qwNumberOfSectors;
} DEVICE, *PDEVICE;
static DEVICE DeviceList[16];
static ULONG dwDevicesCount = 0;
static
BOOL
QueryAllRemovableDevices(VOID)
{
typedef union {
BYTE Buffer[MAX_PATH + sizeof(STORAGE_DEVICE_DESCRIPTOR)];
STORAGE_DEVICE_DESCRIPTOR Descriptor;
} DEVICE_QUERY_INFO, *PDEVICE_QUERY_INFO;
ULONG dwReturnedSize;
WCHAR szPhysicalDrive[20];
DEVICE_QUERY_INFO DeviceInfo;
DISK_GEOMETRY DeviceGeometry;
STORAGE_PROPERTY_QUERY DeviceQuery = {
StorageDeviceProperty,
PropertyStandardQuery,
0
};
for (ULONG_PTR i = 0; i != 15; i++) {
_snwprintf((PVOID)szPhysicalDrive, 20, L"\\??\\PhysicalDrive%lu", i);
HANDLE hDevice = CreateFileW(szPhysicalDrive, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE == hDevice) {
break;
}
if (DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY, &DeviceQuery, sizeof(DeviceQuery), &DeviceInfo, sizeof(DeviceInfo), &dwReturnedSize, NULL)) {
if (DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &DeviceGeometry, sizeof(DeviceGeometry), &dwReturnedSize, NULL)) {
if (DeviceInfo.Descriptor.RemovableMedia) {
if (DeviceInfo.Descriptor.ProductIdOffset) {
PSTR szProdcutId = DeviceInfo.Buffer + DeviceInfo.Descriptor.ProductIdOffset;
SIZE_T cbProductId = strlen(szProdcutId);
DeviceList[dwDevicesCount].szProductId = malloc((cbProductId + 1) * 2);
MultiByteToWideChar(CP_OEMCP, 0, szProdcutId, cbProductId + 1, DeviceList[dwDevicesCount].szProductId, cbProductId + 1);
} else {
DeviceList[dwDevicesCount].szProductId = L"Null";
}
DeviceList[dwDevicesCount].Handle = hDevice;
DeviceList[dwDevicesCount].dwBytesPerSector = DeviceGeometry.BytesPerSector;
DeviceList[dwDevicesCount].qwNumberOfSectors = DeviceGeometry.SectorsPerTrack * DeviceGeometry.TracksPerCylinder * (ULONGLONG)DeviceGeometry.Cylinders.QuadPart;
dwDevicesCount++;
continue;
}
}
}
CloseHandle(hDevice);
}
return dwDevicesCount ? TRUE : FALSE;
}
static
PWSTR
ConvertSizeToUnits(
IN ULONG64 qwSize,
OUT PLDOUBLE ldSize
)
{
if (qwSize >= SIZE_OF_GB) {
*ldSize = (LDOUBLE)qwSize / (LDOUBLE)SIZE_OF_GB;
return L"GB";
} else if (qwSize >= SIZE_OF_MB) {
*ldSize = (LDOUBLE)qwSize / (LDOUBLE)SIZE_OF_MB;
return L"MB";
} else if (qwSize >= SIZE_OF_KB) {
*ldSize = (LDOUBLE)qwSize / (LDOUBLE)SIZE_OF_KB;
return L"KB";
} else {
*ldSize = (LDOUBLE)qwSize;
return L"B";
}
}
static BOOL bUpdateCounter = FALSE;
static
DWORD
WINAPI
OneMinuteTimer(VOID)
{
while (TRUE) {
Sleep(1000 * 60);
bUpdateCounter = TRUE;
}
}
int
wmain()
{
_setmode(_fileno(stdout), _O_U16TEXT);
if (!QueryAllRemovableDevices()) {
wprintf(L"Didn't find any USB drives\n");
getchar();
return -1;
}
ULONG dwUserChoice;
wprintf(L"\n ID │ Name │ Size\n"
L"────┼──────────────────────────────────┼──────────\n"
);
for (ULONG_PTR i = 0; i != dwDevicesCount; i++) {
LDOUBLE ldSize;
PWSTR szUnit = ConvertSizeToUnits(DeviceList[i].dwBytesPerSector * DeviceList[i].qwNumberOfSectors, &ldSize);
wprintf(L" % 2lu │ %-32ls │ %-6.2lf %ls\n", i, DeviceList[i].szProductId, ldSize, szUnit);
}
putwchar(L'\n');
while (TRUE) {
wprintf(L"Choice disk (0 - %lu): ", dwDevicesCount - 1);
scanf("%ld", &dwUserChoice);
if (dwUserChoice < dwDevicesCount) {
break;
}
}
for (ULONG_PTR i = 0; i != dwDevicesCount; i++) {
if (i == dwUserChoice) {
continue;
}
CloseHandle(DeviceList[i].Handle);
}
ULONG dwWritten;
HANDLE hDisk = DeviceList[dwUserChoice].Handle;
SIZE_T cbSector = DeviceList[dwUserChoice].dwBytesPerSector;
ULONG64 qwNumberOfSectors = DeviceList[dwUserChoice].qwNumberOfSectors;
PBYTE Sector = malloc(cbSector);
CreateThread(NULL, 0, (PVOID)&OneMinuteTimer, NULL, 0, NULL);
wprintf(L"\nIt might take several hours\n");
wprintf(L"Phase 1: Writting 0's\n");
memset(Sector, 0, cbSector);
bUpdateCounter = TRUE;
for (ULONG64 i = 0; i != qwNumberOfSectors; i++) {
if (bUpdateCounter) {
LDOUBLE ldDonePercent = (LDOUBLE)i / (LDOUBLE)qwNumberOfSectors;
ULONG_PTR dwCharIndex = 0;
ULONG_PTR dwCharsDone = ldDonePercent * 39.0;
wprintf(L"\r[");
while (dwCharIndex++ != dwCharsDone) {
putwchar(L'■');
}
while (dwCharIndex++ != 39) {
putwchar(L' ');
}
wprintf(L"] %3.0lf/100", ldDonePercent * 100.0);
bUpdateCounter = FALSE;
}
if (!WriteFile(hDisk, Sector, cbSector, &dwWritten, NULL)) {
CloseHandle(hDisk);
wprintf(L"Problem with writting\n");
getchar();
return -2;
}
}
wprintf(L"\r[■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■] 100/100\n");
wprintf(L"Phase 2: Writting binary 1's\n");
memset(Sector, 0xFF, cbSector);
bUpdateCounter = TRUE;
for (ULONG64 i = 0; i != qwNumberOfSectors; i++) {
if (bUpdateCounter) {
LDOUBLE ldDonePercent = (LDOUBLE)i / (LDOUBLE)qwNumberOfSectors;
ULONG_PTR dwCharIndex = 0;
ULONG_PTR dwCharsDone = ldDonePercent * 39.0;
wprintf(L"\r[");
while (dwCharIndex++ != dwCharsDone) {
putwchar(L'■');
}
while (dwCharIndex++ != 39) {
putwchar(L' ');
}
wprintf(L"] %3.0lf/100", ldDonePercent * 100.0);
bUpdateCounter = FALSE;
}
if (!WriteFile(hDisk, Sector, cbSector, &dwWritten, NULL)) {
CloseHandle(hDisk);
wprintf(L"Problem with writting\n");
getchar();
return -2;
}
}
wprintf(L"\r[■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■] 100/100\n");
wprintf(L"Phase 3: Writting random data\n");
bUpdateCounter = TRUE;
for (ULONG64 i = 0; i != qwNumberOfSectors; i++) {
if (bUpdateCounter) {
LDOUBLE ldDonePercent = (LDOUBLE)i / (LDOUBLE)qwNumberOfSectors;
ULONG_PTR dwCharIndex = 0;
ULONG_PTR dwCharsDone = ldDonePercent * 39.0;
wprintf(L"\r[");
while (dwCharIndex++ != dwCharsDone) {
putwchar(L'■');
}
while (dwCharIndex++ != 39) {
putwchar(L' ');
}
wprintf(L"] %3.0lf/100", ldDonePercent * 100.0);
bUpdateCounter = FALSE;
}
if (STATUS_SUCCESS != BCryptGenRandom(NULL, Sector, cbSector, BCRYPT_USE_SYSTEM_PREFERRED_RNG)) {
CloseHandle(hDisk);
wprintf(L"Problem with random generator\n");
getchar();
return -3;
}
if (!WriteFile(hDisk, Sector, cbSector, &dwWritten, NULL)) {
CloseHandle(hDisk);
wprintf(L"Problem with writting\n");
getchar();
return -4;
}
}
wprintf(L"\r[■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■] 100/100\n");
CloseHandle(hDisk);
return 0;
}
EDIT: If you want to use it for hard drives comment out "if (DeviceInfo.Descriptor.RemovableMedia) {" from QueryAllRemovableDevices, also link with brypt.lib and the speed depends from your USB version/hard disk
Another edit: it requires admin privileges, otherwise will not find any drives
RE: USD Wiper with DoD 5220.22-M - darkninja1980 - 04-27-2019
(04-27-2019, 08:26 PM)IsBadWritePtr Wrote: I needed a USB wiper and I coded one really fast, if someone is needs one here is the code. Its not perfect but makes the job right
Code: #include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <windows.h>
#include <winioctl.h>
#include <ntstatus.h>
#define SIZE_OF_GB (SIZE_OF_MB * 1024)
#define SIZE_OF_MB (SIZE_OF_KB * 1024)
#define SIZE_OF_KB (1 * 1024)
typedef double DOUBLE, *PDOUBLE;
typedef long double LDOUBLE, *PLDOUBLE;
typedef struct _DEVICE {
HANDLE Handle;
PWSTR szProductId;
ULONG dwBytesPerSector;
ULONG64 qwNumberOfSectors;
} DEVICE, *PDEVICE;
static DEVICE DeviceList[16];
static ULONG dwDevicesCount = 0;
static
BOOL
QueryAllRemovableDevices(VOID)
{
typedef union {
BYTE Buffer[MAX_PATH + sizeof(STORAGE_DEVICE_DESCRIPTOR)];
STORAGE_DEVICE_DESCRIPTOR Descriptor;
} DEVICE_QUERY_INFO, *PDEVICE_QUERY_INFO;
ULONG dwReturnedSize;
WCHAR szPhysicalDrive[20];
DEVICE_QUERY_INFO DeviceInfo;
DISK_GEOMETRY DeviceGeometry;
STORAGE_PROPERTY_QUERY DeviceQuery = {
StorageDeviceProperty,
PropertyStandardQuery,
0
};
for (ULONG_PTR i = 0; i != 15; i++) {
_snwprintf((PVOID)szPhysicalDrive, 20, L"\\??\\PhysicalDrive%lu", i);
HANDLE hDevice = CreateFileW(szPhysicalDrive, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE == hDevice) {
break;
}
if (DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY, &DeviceQuery, sizeof(DeviceQuery), &DeviceInfo, sizeof(DeviceInfo), &dwReturnedSize, NULL)) {
if (DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &DeviceGeometry, sizeof(DeviceGeometry), &dwReturnedSize, NULL)) {
if (DeviceInfo.Descriptor.RemovableMedia) {
if (DeviceInfo.Descriptor.ProductIdOffset) {
PSTR szProdcutId = DeviceInfo.Buffer + DeviceInfo.Descriptor.ProductIdOffset;
SIZE_T cbProductId = strlen(szProdcutId);
DeviceList[dwDevicesCount].szProductId = malloc((cbProductId + 1) * 2);
MultiByteToWideChar(CP_OEMCP, 0, szProdcutId, cbProductId + 1, DeviceList[dwDevicesCount].szProductId, cbProductId + 1);
} else {
DeviceList[dwDevicesCount].szProductId = L"Null";
}
DeviceList[dwDevicesCount].Handle = hDevice;
DeviceList[dwDevicesCount].dwBytesPerSector = DeviceGeometry.BytesPerSector;
DeviceList[dwDevicesCount].qwNumberOfSectors = DeviceGeometry.SectorsPerTrack * DeviceGeometry.TracksPerCylinder * (ULONGLONG)DeviceGeometry.Cylinders.QuadPart;
dwDevicesCount++;
continue;
}
}
}
CloseHandle(hDevice);
}
return dwDevicesCount ? TRUE : FALSE;
}
static
PWSTR
ConvertSizeToUnits(
IN ULONG64 qwSize,
OUT PLDOUBLE ldSize
)
{
if (qwSize >= SIZE_OF_GB) {
*ldSize = (LDOUBLE)qwSize / (LDOUBLE)SIZE_OF_GB;
return L"GB";
} else if (qwSize >= SIZE_OF_MB) {
*ldSize = (LDOUBLE)qwSize / (LDOUBLE)SIZE_OF_MB;
return L"MB";
} else if (qwSize >= SIZE_OF_KB) {
*ldSize = (LDOUBLE)qwSize / (LDOUBLE)SIZE_OF_KB;
return L"KB";
} else {
*ldSize = (LDOUBLE)qwSize;
return L"B";
}
}
static BOOL bUpdateCounter = FALSE;
static
DWORD
WINAPI
OneMinuteTimer(VOID)
{
while (TRUE) {
Sleep(1000 * 60);
bUpdateCounter = TRUE;
}
}
int
wmain()
{
_setmode(_fileno(stdout), _O_U16TEXT);
if (!QueryAllRemovableDevices()) {
wprintf(L"Didn't find any USB drives\n");
getchar();
return -1;
}
ULONG dwUserChoice;
wprintf(L"\n ID │ Name │ Size\n"
L"────┼──────────────────────────────────┼──────────\n"
);
for (ULONG_PTR i = 0; i != dwDevicesCount; i++) {
LDOUBLE ldSize;
PWSTR szUnit = ConvertSizeToUnits(DeviceList[i].dwBytesPerSector * DeviceList[i].qwNumberOfSectors, &ldSize);
wprintf(L" % 2lu │ %-32ls │ %-6.2lf %ls\n", i, DeviceList[i].szProductId, ldSize, szUnit);
}
putwchar(L'\n');
while (TRUE) {
wprintf(L"Choice disk (0 - %lu): ", dwDevicesCount - 1);
scanf("%ld", &dwUserChoice);
if (dwUserChoice < dwDevicesCount) {
break;
}
}
for (ULONG_PTR i = 0; i != dwDevicesCount; i++) {
if (i == dwUserChoice) {
continue;
}
CloseHandle(DeviceList[i].Handle);
}
ULONG dwWritten;
HANDLE hDisk = DeviceList[dwUserChoice].Handle;
SIZE_T cbSector = DeviceList[dwUserChoice].dwBytesPerSector;
ULONG64 qwNumberOfSectors = DeviceList[dwUserChoice].qwNumberOfSectors;
PBYTE Sector = malloc(cbSector);
CreateThread(NULL, 0, (PVOID)&OneMinuteTimer, NULL, 0, NULL);
wprintf(L"\nIt might take several hours\n");
wprintf(L"Phase 1: Writting 0's\n");
memset(Sector, 0, cbSector);
bUpdateCounter = TRUE;
for (ULONG64 i = 0; i != qwNumberOfSectors; i++) {
if (bUpdateCounter) {
LDOUBLE ldDonePercent = (LDOUBLE)i / (LDOUBLE)qwNumberOfSectors;
ULONG_PTR dwCharIndex = 0;
ULONG_PTR dwCharsDone = ldDonePercent * 39.0;
wprintf(L"\r[");
while (dwCharIndex++ != dwCharsDone) {
putwchar(L'■');
}
while (dwCharIndex++ != 39) {
putwchar(L' ');
}
wprintf(L"] %3.0lf/100", ldDonePercent * 100.0);
bUpdateCounter = FALSE;
}
if (!WriteFile(hDisk, Sector, cbSector, &dwWritten, NULL)) {
wprintf(L"Problem with writting\n");
getchar();
return -2;
}
}
wprintf(L"\r[■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■] 100/100\n");
wprintf(L"Phase 2: Writting binary 1's\n");
memset(Sector, 0xFF, cbSector);
bUpdateCounter = TRUE;
for (ULONG64 i = 0; i != qwNumberOfSectors; i++) {
if (bUpdateCounter) {
LDOUBLE ldDonePercent = (LDOUBLE)i / (LDOUBLE)qwNumberOfSectors;
ULONG_PTR dwCharIndex = 0;
ULONG_PTR dwCharsDone = ldDonePercent * 39.0;
wprintf(L"\r[");
while (dwCharIndex++ != dwCharsDone) {
putwchar(L'■');
}
while (dwCharIndex++ != 39) {
putwchar(L' ');
}
wprintf(L"] %3.0lf/100", ldDonePercent * 100.0);
bUpdateCounter = FALSE;
}
if (!WriteFile(hDisk, Sector, cbSector, &dwWritten, NULL)) {
wprintf(L"Problem with writting\n");
getchar();
return -2;
}
}
wprintf(L"\r[■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■] 100/100\n");
wprintf(L"Phase 3: Writting random data\n");
bUpdateCounter = TRUE;
for (ULONG64 i = 0; i != qwNumberOfSectors; i++) {
if (bUpdateCounter) {
LDOUBLE ldDonePercent = (LDOUBLE)i / (LDOUBLE)qwNumberOfSectors;
ULONG_PTR dwCharIndex = 0;
ULONG_PTR dwCharsDone = ldDonePercent * 39.0;
wprintf(L"\r[");
while (dwCharIndex++ != dwCharsDone) {
putwchar(L'■');
}
while (dwCharIndex++ != 39) {
putwchar(L' ');
}
wprintf(L"] %3.0lf/100", ldDonePercent * 100.0);
bUpdateCounter = FALSE;
}
if (STATUS_SUCCESS != BCryptGenRandom(NULL, Sector, cbSector, BCRYPT_USE_SYSTEM_PREFERRED_RNG)) {
CloseHandle(hDisk);
wprintf(L"Problem with random generator\n");
getchar();
return -3;
}
if (!WriteFile(hDisk, Sector, cbSector, &dwWritten, NULL)) {
wprintf(L"Problem with writting\n");
getchar();
return -4;
}
}
wprintf(L"\r[■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■] 100/100\n");
CloseHandle(hDisk);
return 0;
}
nice and clean readable code.
RE: USD Wiper with DoD 5220.22-M - 0xDEAD10CC - 04-28-2019
Looks interesting first glance. You're not closing some handles before your return statements if WriteFile() fails for hDisk though.
edit: I also don't understand some of your casts..
Code: _snwprintf((PVOID)szPhysicalDrive, 20, L"\\??\\PhysicalDrive%lu", i);
I know this was written for C but why case to a void pointer when the type is already WCHAR *? Also not sure of the ULONG_PTR usage.
RE: USD Wiper with DoD 5220.22-M - IsBadWritePtr - 04-29-2019
All handles are closes when process is terminated, so there is no need of freeing heap and closing handles, It did give my some warning and PVOID fixes all cast warnings, abd ULONG_PTR is 64 bit on x64 bit program and 32 bit on x32 program Its just my style
RE: USD Wiper with DoD 5220.22-M - 0xDEAD10CC - 04-29-2019
(04-29-2019, 05:15 AM)IsBadWritePtr Wrote: All handles are closes when process is terminated, so there is no need of freeing heap and closing handles, It did give my some warning and PVOID fixes all cast warnings, abd ULONG_PTR is 64 bit on x64 bit program and 32 bit on x32 program Its just my style
I know what ULONG_PTR is for but it's entirely redundant here (you don't need the difference in size for platform variances; and it would be like writing a loop -- for (int *p = 0; p < N; ++p) just to have a numeric value). Your code flow is half implemented for best practice on freeing handles. Casting to PVOID only works because in C void pointers can be assigned to any pointer type without a cast, but it's entirely redundant since the existing type was already of the appropriate type of WCHAR* in this case.
The errors (if any) that you were getting were not from that, and a "fix" like that implies you're just doing by trial and error here. I didn't comment on the rest of the code for my own reasons.
RE: USD Wiper with DoD 5220.22-M - IsBadWritePtr - 04-29-2019
(04-29-2019, 06:02 AM)0xDEAD10CC Wrote: (04-29-2019, 05:15 AM)IsBadWritePtr Wrote: All handles are closes when process is terminated, so there is no need of freeing heap and closing handles, It did give my some warning and PVOID fixes all cast warnings, abd ULONG_PTR is 64 bit on x64 bit program and 32 bit on x32 program Its just my style
I know what ULONG_PTR is for but it's entirely redundant here (you don't need the difference in size for platform variances). Your code flow is half implemented for best practice on freeing handles. And casting to PVOID only works because in C void pointers can be assigned to any pointer type without a cast, but it's entirely redundant since the existing type was already of the appropriate type of WCHAR*. The errors (if any) that you were getting were not from that, and a "fix" like that implies you're just doing by trial and error here.
ULONG_PTR is just how i write it, before i used ULONG but thought ULONG_PTR look better, also i checked there was no warning maybe i used another function and there was cast warning I don't remember and there is no needed even to close the handles since windows does that on termination and 1 or 2 handled wont waste much memory
RE: USD Wiper with DoD 5220.22-M - 0xDEAD10CC - 04-29-2019
>> ULONG_PTR is just how i write it, before i used ULONG but thought ULONG_PTR look better
>> there is no needed even to close the handles since windows does that on termination
Lol, I won't bother to argue best practice semantics with you then, as these comments say enough.
RE: USD Wiper with DoD 5220.22-M - IsBadWritePtr - 04-29-2019
Making ULONG to ULONG_PTR don't see the problem, also with the for i < n, i <= n, i != n there is no difference, it does it N number times
RE: USD Wiper with DoD 5220.22-M - 0xDEAD10CC - 04-29-2019
(04-29-2019, 06:40 AM)IsBadWritePtr Wrote: Making ULONG to ULONG_PTR don't see the problem, also with the for i < n, i <= n, i != n there is no difference, it does it N number times
I never pointed out that < vs != was wrong. If you read what I posted it was actually to emphasize the pointer semantics: for (int *p = 0; p < N; ++p). This is essentially what you're doing, which is strange. Software developers don't do stuff like this because it "looks better" either.
What you aren't understanding; is contextual semantics can benefit a program for readability and using proper types too. You could have also used a regular pointer as I said, or even a char for that matter, but who does that? It makes absolutely no sense to do so.
RE: USD Wiper with DoD 5220.22-M - IsBadWritePtr - 04-29-2019
Yea, english is not my native and was too early in the morning to give enough attention on your comments, I find strange how ULONG_PTR tilts you, and using char or short is very bad design for a for loop and that thing will bite you in the a**, so you don't mind to be used char, but you find confusing ULONG_PTR ? "You could have also used a regular pointer" ULONG_PTR has the same size as a pointer (does that count ?) but its increase by 1 not by its type (int* will increase by 4 bytes, this is what i mean), if i know this thing will start a war between int and ULONG_PTR will start, i would have mixed tabs with spaces to pour salt in everyones wound, a little bit of VIM vs EMACS, and say that GCC sucks. Are you the type of guy that uses signed int ?
4 spaces > 1 tab
VIM > EMACS
Watcom > GCC
Windows > Linux
Edge > Chrome
Starwars suck
|