Code:
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
namespace UnpackNdt
{
class Program
{
private static readonly int[] shufflingMap = { 1, 3, 2, 3, 1, 5, 4, 2, 1, 4, 2, 8, 4, 2, 6, 8, 2, 6, 4 };
private const int debugLength = 0;
private const int wordLength = 4;
private const int headerLength = 24;
private const UInt32 keyAdjustment = (UInt32)0x57D3CEFF;
private const UInt32 ncrMagicBytes = 0x0052434e;
private const UInt32 v3Bytes = 0x00030001;
private const byte defaultEncKey = 82;
[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct NdtFileHeader
{
public UInt32 MagicBytes;
public UInt32 Version;
public UInt32 FileSize;
public byte Key;
public byte Undefined1;
public byte Undefined2;
public byte Undefined3;
public UInt32 Undefined4;
public UInt32 Undefined5;
}
static void Main(string[] args)
{
if (args.Length != 1)
{
Console.WriteLine("No file path provided as single argument");
return;
}
string inputFilePath = args[0];
applyFor(inputFilePath);
}
static void runCycle(string inputFilePath) {
if (Path.GetExtension(inputFilePath).ToLowerInvariant() == ".ndt") {
string dirName = Path.GetDirectoryName(inputFilePath);
string fileName = Path.GetFileNameWithoutExtension(inputFilePath);
string decryptedFilePath = applyFor(inputFilePath);
string encryptedFilePath = applyFor(decryptedFilePath);
string resultFilePath =applyFor(encryptedFilePath);
}
}
static string applyFor(string inputFilePath) {
Console.WriteLine("Executing for: " + Path.GetFileName(inputFilePath));
string dirName = Path.GetDirectoryName(inputFilePath);
string fileName = Path.GetFileNameWithoutExtension(inputFilePath);
string outputFilePath;
if (Path.GetExtension(inputFilePath).ToLowerInvariant() == ".ndt") {
outputFilePath = Path.Combine(dirName, fileName + ".txt");
byte[] input = File.ReadAllBytes(inputFilePath);
byte[] output = Decrypt(input);
File.WriteAllBytes(outputFilePath, output);
} else {
outputFilePath = Path.Combine(dirName, fileName + "2.ndt");
byte[] input = File.ReadAllBytes(inputFilePath);
byte[] output = EncryptV3(input, defaultEncKey);
File.WriteAllBytes(outputFilePath, output);
}
return outputFilePath;
}
static byte[] deshuffle(byte[] input) {
int inputLength = input.Length;
byte[] output = new byte[inputLength];
Array.Copy(input, output, inputLength);
int shufflingMapLength = shufflingMap.Length;
int numberOfWords = inputLength / wordLength;
for (int i = numberOfWords - 1; i >= 0; i--) {
int shuffleBy = shufflingMap[i % shufflingMapLength];
int j = (i + shuffleBy) % numberOfWords;
int firstPos = i * wordLength;
int secondPos = j * wordLength;
UInt32 first = GetUInt32(output, firstPos, wordLength);
UInt32 second = GetUInt32(output, secondPos, wordLength);
SetUInt32(second, output, firstPos, wordLength);
SetUInt32(first, output, secondPos, wordLength);
}
return output;
}
static byte[] shuffle(byte[] input) {
int inputLength = input.Length;
byte[] output = new byte[inputLength];
Array.Copy(input, output, inputLength);
int shufflingMapLength = shufflingMap.Length;
int numberOfWords = inputLength / wordLength;
for (int i = 0; i < numberOfWords; i++) {
int shuffleBy = shufflingMap[i % shufflingMapLength];
int j = (i + shuffleBy) % numberOfWords;
int firstPos = i * wordLength;
int secondPos = j * wordLength;
UInt32 first = GetUInt32(output, firstPos, wordLength);
UInt32 second = GetUInt32(output, secondPos, wordLength);
SetUInt32(second, output, firstPos, wordLength);
SetUInt32(first, output, secondPos, wordLength);
}
return output;
}
static byte[] decryptV3(byte[] input, byte key) {
int inputLength = input.Length;
byte[] output = new byte[inputLength];
Array.Copy(input, output, inputLength);
UInt32 adjustedKey = key - keyAdjustment;
UInt32 sum = 0;
UInt32 carryOver = 0;
int numberOfWords = inputLength / wordLength;
for (int i = 0; i < numberOfWords; i++) {
int currentPos = i * wordLength;
UInt32 encoded = GetUInt32(output, currentPos, wordLength);
UInt32 word = adjustedKey + encoded + sum + carryOver;
SetUInt32(word, output, currentPos, wordLength);
sum += key;
carryOver = encoded;
if (i < debugLength) {
Console.WriteLine($"word={word}, encoded={encoded}, sum={sum}");
}
}
return output;
}
static byte[] encryptV3(byte[] input, byte key) {
int inputLength = input.Length;
byte[] output = new byte[inputLength];
Array.Copy(input, output, inputLength);
UInt32 adjustedKey = key - keyAdjustment;
UInt32 sum = 0;
UInt32 carryOver = 0;
int numberOfWords = inputLength / wordLength;
for (int i = 0; i < numberOfWords; i++) {
int currentPos = i * wordLength;
UInt32 word = GetUInt32(output, currentPos, wordLength);
UInt32 encoded = keyAdjustment - key + word - sum - carryOver;
SetUInt32(encoded, output, currentPos, wordLength);
sum += key;
carryOver = encoded;
if (i < debugLength) {
Console.WriteLine($"word={word}, encoded={encoded}, sum={sum}");
}
}
return output;
}
static byte[] Decrypt(byte[] input)
{
int inputLength = input.Length;
int outputLength = inputLength - headerLength;
byte[] output = new byte[outputLength];
// extract header
byte[] headerArray = new byte[headerLength];
Array.Copy(input, headerArray, headerLength);
NdtFileHeader header;
FromArray(headerArray, out header);
Console.WriteLine("Decrypting with header:\n" + string.Join(", ", headerArray));
Console.WriteLine($"Header details: version={header.Version:x}, key={header.Key}");
if (header.Version == 0x00010001) {
for (int i = 0; i < outputLength; i++) {
byte c = input[i + headerLength];
c -= header.Key;
if (i + 1 != outputLength)
{
byte next = input[i + 1 + headerLength];
c ^= next;
}
output[i] = c;
}
}
else if (header.Version == v3Bytes) {
int shufflingMapLength = shufflingMap.Length;
int numberOfDwords = outputLength / 4;
Array.Copy(input, headerLength, output, 0, outputLength);
output = deshuffle(output);
output = decryptV3(output, header.Key);
} else {
Console.WriteLine($"No supported: version={header.Version:x}");
return null;
}
return output;
}
static UInt32 GetUInt32(byte[] array, int index, int length)
{
UInt32 result = 0;
for (int i = length; i > 0; i--)
{
result = result << 8;
result += array[index + i - 1];
}
return result;
}
static void SetUInt32(UInt32 value, byte[] array, int index, int length)
{
for (int i = 0; i < length; i++)
{
array[index + i] = (byte)(value % 256);
value = value / 256;
}
}
static byte[] EncryptV3(byte[] input, byte key)
{
int inputLength = input.Length;
int outputLength = inputLength + headerLength;
byte[] output = new byte[outputLength];
NdtFileHeader header = new NdtFileHeader();
header.MagicBytes = ncrMagicBytes;
header.Version = v3Bytes;
header.FileSize = (uint) inputLength;
header.Key = key;
byte[] headerArray = new byte[headerLength];
ToArray(header, headerArray);
Array.Copy(headerArray, output, headerLength);
byte[] tempOutput = new byte[inputLength];
Array.Copy(input, tempOutput, inputLength);
tempOutput = encryptV3(tempOutput, key);
tempOutput = shuffle(tempOutput);
Array.Copy(tempOutput, 0, output, headerLength, inputLength);
return output;
}
private static void FromArray<T>(byte[] input, out T output)
{
// Pin the managed memory while, copy it out the data, then unpin it
GCHandle handle = GCHandle.Alloc(input, GCHandleType.Pinned);
output = (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
}
private static void ToArray<T>(T input, byte[] output)
{
// Pin the managed memory while, copy it out the data, then unpin it
GCHandle handle = GCHandle.Alloc(output, GCHandleType.Pinned);
Marshal.StructureToPtr(input, handle.AddrOfPinnedObject(), false);
handle.Free();
}
}
}