/* | |
NCF Crypt | |
========= | |
NCF Crypt is a tiny tool to encrypt or decrypt ncf files so they can be | |
validated in CFToolbox or to play the game with Steam. | |
Why do we need this tool? | |
------------------------- | |
Some games on Steam are encrytped so they can be preloaded even before the game | |
has been released. After the release Steam decrypts all files but in some cases | |
flags in the NCF file aren't set properly. That means that even if you have the | |
decrypted files steam and other tools will recognize them as encrytped just | |
becaues the NCF file says so. This tool only changes the flags to and does not | |
decrypt associated files. | |
Usage | |
----- | |
Just drag a NCF file on the executable and it will be decrytpted or encrytped. | |
### Advanced | |
ncfcrypt [/S | /E | /D] filenames | |
filenames - one ore more files to be crypted | |
/S - Swaps the encryption flag for each entry (default behavior) | |
/E - Encrypts all entries | |
/D - Decrypts all entires | |
It's recommned to backup ncf files if you use /E or /D because mixed | |
attributes cannot be restored after encryption or decrypting the whole file. | |
*/ | |
#include <Windows.h> | |
// Custom entry for smaller executable size. | |
#pragma comment(linker, "/entry:Start") | |
// NCF file format: | |
// http://singularity.us.to/dev/steam/Development.Non-Cache-File-Format.ashx.htm | |
enum { | |
FLAG_CONFIG = 1 << 0, | |
FLAG_LAUNCH = 1 << 1, | |
FLAG_UNKNOWN1 = 1 << 2, | |
FLAG_LOCKED = 1 << 3, | |
FLAG_UNKNOWN2 = 1 << 4, | |
FLAG_NOCACHE = 1 << 5, | |
FLAG_BACKUP = 1 << 6, | |
FLAG_PURGE = 1 << 7, | |
FLAG_ENCRYPTED = 1 << 8, | |
FLAG_READONLY = 1 << 9, | |
FLAG_HIDDEN = 1 << 10, | |
FLAG_EXECUTABLE = 1 << 11, | |
FLAG_UNKNOWN3 = 1 << 12, | |
FLAG_UNKNOWN4 = 1 << 13, | |
FLAG_FILE = 1 << 14 | |
}; | |
typedef struct _FILE_HEADER { | |
DWORD dwHeaderVersion; | |
DWORD dwCacheType; | |
DWORD dwFormatVersion; | |
DWORD dwApplicationID; | |
DWORD dwApplicationVersion; | |
DWORD dwIsMounted; | |
DWORD dwDummy0; | |
DWORD dwFileSize; | |
DWORD dwClusterSize; | |
DWORD dwClusterCount; | |
DWORD dwChecksum; | |
} FILE_HEADER, *PFILE_HEADER; | |
typedef struct _MANIFEST_HEADER { | |
DWORD dwHeaderVersion; | |
DWORD dwApplicationID; | |
DWORD dwApplicationVersion; | |
DWORD dwNodeCount; | |
DWORD dwFileCount; | |
DWORD dwCompressionBlockSize; | |
DWORD dwBinarySize; | |
DWORD dwNameSize; | |
DWORD dwHashTableKeyCount; | |
DWORD dwNumOfMinimumFootprintFiles; | |
DWORD dwNumOfUserConfigFiles; | |
DWORD dwBitmask; | |
DWORD dwFingerprint; | |
DWORD dwChecksum; | |
} MANIFEST_HEADER, *PMANIFEST_HEADER; | |
typedef struct _MANIFEST_NODE { | |
DWORD dwNameOffset; | |
DWORD dwCountOrSize; | |
DWORD dwFileId; | |
DWORD dwAttributes; | |
DWORD dwParentIndex; | |
DWORD dwNextIndex; | |
DWORD dwChildIndex; | |
} MANIFEST_NODE, *PMANIFEST_NODE; | |
static BOOL ValidateFileHeader(PFILE_HEADER pFileHeader) | |
{ | |
DWORD i, dwChecksum = 0; | |
PBYTE data = (PBYTE)pFileHeader; | |
// add every single byte to the checksum | |
for (i = 0; i < sizeof(FILE_HEADER) - sizeof(DWORD); i++) { | |
dwChecksum += data[i]; | |
} | |
return pFileHeader->dwChecksum == dwChecksum; | |
} | |
enum { | |
OPTION_SWAP, | |
OPTION_ENCRYPT, | |
OPTION_DECRYPT | |
}; | |
static void Swap(PMANIFEST_NODE rgManifestNodes, UINT dwManifestNodes) | |
{ | |
DWORD i; | |
for (i = 0; i < dwManifestNodes; i++) { | |
// folders don't have any flags, do nothing | |
if (!rgManifestNodes[i].dwAttributes) | |
continue; | |
if (rgManifestNodes[i].dwAttributes & FLAG_ENCRYPTED) | |
rgManifestNodes[i].dwAttributes ^= FLAG_ENCRYPTED; | |
else | |
rgManifestNodes[i].dwAttributes |= FLAG_ENCRYPTED; | |
} | |
} | |
static void Encrypt(PMANIFEST_NODE rgManifestNodes, UINT dwManifestNodes) | |
{ | |
DWORD i; | |
for (i = 0; i < dwManifestNodes; i++) { | |
// folders don't have any flags, do nothing | |
if (!rgManifestNodes[i].dwAttributes) | |
continue; | |
if (!(rgManifestNodes[i].dwAttributes & FLAG_ENCRYPTED)) | |
rgManifestNodes[i].dwAttributes |= FLAG_ENCRYPTED; | |
} | |
} | |
static void Decrypt(PMANIFEST_NODE rgManifestNodes, UINT dwManifestNodes) | |
{ | |
DWORD i; | |
for (i = 0; i < dwManifestNodes; i++) { | |
// folders don't have any flags, do nothing | |
if (!rgManifestNodes[i].dwAttributes) | |
continue; | |
if (rgManifestNodes[i].dwAttributes & FLAG_ENCRYPTED) | |
rgManifestNodes[i].dwAttributes ^= FLAG_ENCRYPTED; | |
} | |
} | |
static void CryptFile(PCWSTR pszFileName, DWORD dwOption) | |
{ | |
HANDLE hFile, hFileMapping; | |
PVOID pBaseAddress; | |
PBYTE pData; | |
PFILE_HEADER pFileHeader; | |
PMANIFEST_HEADER pManifestHeader; | |
PMANIFEST_NODE grpManifestNodes; | |
// open file | |
hFile = CreateFileW(pszFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, | |
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); | |
if (hFile == INVALID_HANDLE_VALUE) | |
FatalAppExitW(0, L"Opening file failed"); | |
// map into memory | |
hFileMapping = CreateFileMappingW(hFile, NULL, PAGE_READWRITE, 0, 0, NULL); | |
if (!hFileMapping) | |
FatalAppExitW(0, L"Cannot map file in memory"); | |
pBaseAddress = MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0, 0, 0); | |
pData = (PBYTE)pBaseAddress; | |
// FileHeader | |
pFileHeader = (PFILE_HEADER)pData; | |
pData += sizeof(FILE_HEADER); | |
// check if file is a valid ncf file | |
if (!ValidateFileHeader(pFileHeader)) | |
FatalAppExitW(0, L"File is not a valid ncf"); | |
// ManifestHeader | |
pManifestHeader = (PMANIFEST_HEADER)pData; | |
pData += sizeof(MANIFEST_HEADER); | |
// ManifestNodes | |
grpManifestNodes = (PMANIFEST_NODE)pData; | |
pData += (sizeof(MANIFEST_NODE) * pManifestHeader->dwNodeCount); | |
switch (dwOption) { | |
case OPTION_SWAP: | |
Swap(grpManifestNodes, pManifestHeader->dwNodeCount); | |
break; | |
case OPTION_ENCRYPT: | |
Encrypt(grpManifestNodes, pManifestHeader->dwNodeCount); | |
break; | |
case OPTION_DECRYPT: | |
Decrypt(grpManifestNodes, pManifestHeader->dwNodeCount); | |
break; | |
} | |
// write and close file | |
dwOption = UnmapViewOfFile(pBaseAddress); | |
CloseHandle(hFileMapping); | |
CloseHandle(hFile); | |
} | |
int WINAPI Start(void) | |
{ | |
PWSTR *ppszArgs; | |
DWORD dwOption, i, iNumArgs; | |
ppszArgs = CommandLineToArgvW(GetCommandLineW(), &iNumArgs); | |
dwOption = OPTION_SWAP; | |
for (i = 1; i < iNumArgs; i++) { | |
if (ppszArgs[i][0] == '/') { | |
switch (ppszArgs[i][1]) { | |
case 'S': dwOption = OPTION_SWAP; break; | |
case 'E': dwOption = OPTION_ENCRYPT; break; | |
case 'D': dwOption = OPTION_DECRYPT; break; | |
} | |
} else { | |
CryptFile(ppszArgs[i], dwOption); | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment