Transmutate/Program.cs

606 lines
25 KiB
C#

using System.Collections;
using System.Diagnostics;
using System.Text;
namespace Transmutate
{
public class Program
{
public static int succeededFiles = 0;
public static int failedFiles = 0;
static byte[] BlankArea =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF
};
static byte[] ProgramBlueKey =
{
0x85, 0x7D, 0x67, 0x9B, 0x6E, 0x96, 0x73, 0xA1, 0xD3, 0xCD, 0xBA, 0x75, 0x98, 0x9C, 0x83, 0x70, 0xA0, 0x9C, 0x5D, 0x9E, 0x91, 0x68, 0x9E, 0x91, 0x88, 0x8D, 0x8A, 0xA9, 0x90, 0x64, 0x6A, 0xA8, 0x9B
};
public static bool FFMpegCheck()
{
Process checkProcess = new Process();
checkProcess.StartInfo.FileName = "ffmpeg";
checkProcess.StartInfo.Arguments = "-loglevel quiet";
try { checkProcess.Start(); }
catch { return false; }
return true;
}
public static void Main(string[] args)
{
Console.WriteLine("Transmutate Show Converter v1.2");
if (!FFMpegCheck())
{
Console.WriteLine("FFMpeg was not found. Please check README.txt. Press any key to close.");
Console.ReadKey();
return;
}
if (args.Length == 0)
{
Console.WriteLine("No files specified. Press any key to close.");
Console.ReadKey();
return;
}
if (args[0] == "special")
{
Console.WriteLine("Performing special function " + args[1]);
SpecialFunction(args[1], args[2], args.Length == 4 ? args[3] : "");
Console.WriteLine("Complete.");
return;
}
foreach (string enumFile in args)
{
if (!File.Exists(enumFile))
{
Console.WriteLine($"Specified file {enumFile} does not exist. Press any key to continue.");
Console.ReadKey();
failedFiles++;
continue;
}
switch (enumFile.Split(".").Last())
{
case "shw":
Console.WriteLine($"Converting {Path.GetFileNameWithoutExtension(enumFile)} from ProgramBlue to RR-Engine.");
ProgramBlueToRR(enumFile);
break;
case "rshw":
case "cshw":
case "sshw":
case "nshw":
Console.WriteLine($"Converting {Path.GetFileNameWithoutExtension(enumFile)} from RR-Engine to ProgramBlue.");
RRToProgramBlue(enumFile);
break;
default:
Console.WriteLine("Unsupported format. Press any key to continue.");
Console.ReadKey();
failedFiles++;
continue;
}
}
Console.WriteLine($"Complete. {succeededFiles} Converted, {failedFiles} Failed.");
}
public static void SpecialFunction(string function, string argument, string filePath)
{
if (function == "decode")
{
List<byte> outData = new List<byte>();
byte[] fullFile = File.ReadAllBytes(filePath);
switch (argument)
{
case "old":
foreach (byte b in fullFile) outData.Add((byte)(b - 0x36));
break;
case "new":
long keyIndex = 0;
foreach (byte b in fullFile)
{
outData.Add((byte)(b - (ProgramBlueKey[keyIndex % 33])));
keyIndex++;
}
break;
}
File.WriteAllBytes("pb_decoded.dat", outData.ToArray());
}
else if (function == "reconstruct")
{
List<byte> outData = new List<byte>();
UTF8Encoding utf8 = new UTF8Encoding();
List<byte> audioData = new List<byte>(File.ReadAllBytes("pboutaudio.tmp"));
while (audioData.Last() == 0)
{
audioData.RemoveAt(audioData.Count - 1);
}
string versionFooter = "<v5.01>";
int dataIndex = audioData.Count + 0x7D;
string veryTopHeader = dataIndex.ToString() + "<dsfa>";
outData.AddRange(utf8.GetBytes(veryTopHeader));
outData.AddRange(audioData);
outData.AddRange(BlankArea);
outData.AddRange(ProgramBlue501Obfuscate(File.ReadAllBytes("pboutdata.xml")));
outData.AddRange(utf8.GetBytes(versionFooter));
File.WriteAllBytes("reconstructed", outData.ToArray());
}
}
public static void ProgramBlueToRR(string filePath)
{
string fileCheckAgainst = File.ReadAllText(filePath);
string verString = fileCheckAgainst.Split("<").Last().Split(">")[0];
bool mp4Present = fileCheckAgainst.Split(">").Last().Contains("mp4");
switch (verString)
{
case "v2.20":
ProgramBlue220Decode(filePath);
break;
/*case "v4.71":
ProgramBlue471Decode(filePath);
break;
case "v5.00":*/
case "v5.01":
ProgramBlue501Decode(filePath, mp4Present);
break;
default:
Console.WriteLine($"Unsupported ProgramBlue Version \"{verString}\".\nPlease open and re-save this showtape in ProgramBlue v5.41.\nPress any key to continue.");
Console.ReadKey();
failedFiles++;
return;
}
if (File.Exists("rshwin.wav")) File.Delete("rshwin.wav");
Process ffmpegProcess = new Process();
ffmpegProcess.StartInfo.FileName = "ffmpeg";
ffmpegProcess.StartInfo.Arguments = "-loglevel quiet -i pboutaudio.tmp rshwin.wav";
ffmpegProcess.Start();
ffmpegProcess.WaitForExit();
string[] pbXmlData = File.ReadAllLines("pboutdata.xml");
string channelLine = "";
bool readingDataFlag = false;
bool readingCHMLine = false;
List<BitArray> debitted = new List<BitArray>();
int frame = 0;
foreach (string line in pbXmlData)
{
//Console.WriteLine(line);
if (line.Trim() == "<DSFROBOTSDATA>")
{
readingDataFlag = true;
continue;
}
if (line.Trim() == "</DSFROBOTSDATA>")
{
readingDataFlag = false;
continue;
}
if (line.Trim() == "<DSFROBOTSCTM>")
{
readingCHMLine = true;
continue;
}
if (readingDataFlag)
{
if (line.Trim() == "") continue;
debitted.Add(new BitArray(256));
int bitIndex = 0;
foreach (char bit in line.ToCharArray())
{
debitted[frame].Set(bitIndex, bit == '1');
bitIndex++;
}
frame++;
}
if (readingCHMLine)
{
channelLine = line;
readingCHMLine = false;
}
}
string fileExtension = ".rshw";
int[] bitMapping = {};
switch (channelLine)
{
case "1=Mitzi^Spotlight=0xff0000":
bitMapping = BitMappings.ProgramBlueToRRBitMapping_RAE;
fileExtension = ".rshw";
break;
case "1=Chuck^Mouth=0xff0000":
bitMapping = BitMappings.ProgramBlueToRRBitMapping_CYBERS;
fileExtension = ".cshw";
break;
case "1=Wink^Spotlight=0xff0000":
bitMapping = BitMappings.ProgramBlueToRRBitMapping_3ST;
fileExtension = ".rshw";
break;
case "1=CEC^Right^Arm^Out=0xff0000":
bitMapping = BitMappings.ProgramBlueToRRBitMapping_STUDIOC;
fileExtension = ".sshw";
break;
case "1=Mitzi^Lids^Open=0xff0000":
bitMapping = BitMappings.ProgramBlueToRRBitMapping_NRAE;
fileExtension = ".nshw";
break;
default:
Console.WriteLine("Could not automatically determine bit mapping.");
bool pickedBitMapping = false;
while (!pickedBitMapping)
{
Console.WriteLine("Please specify a channel mapping:\nrae\nptt\nr12\nstudioc\n3st\nnrae\n");
Console.Write("> ");
string pick = Console.ReadLine();
switch (pick)
{
case "rae":
bitMapping = BitMappings.ProgramBlueToRRBitMapping_RAE;
fileExtension = ".rshw";
pickedBitMapping = true;
break;
case "r12":
case "ptt":
bitMapping = BitMappings.ProgramBlueToRRBitMapping_CYBERS;
fileExtension = ".cshw";
pickedBitMapping = true;
break;
case "3st":
bitMapping = BitMappings.ProgramBlueToRRBitMapping_3ST;
fileExtension = ".rshw";
pickedBitMapping = true;
break;
case "nrae":
bitMapping = BitMappings.ProgramBlueToRRBitMapping_NRAE;
fileExtension = ".nshw";
pickedBitMapping = true;
break;
case "studioc":
bitMapping = BitMappings.ProgramBlueToRRBitMapping_STUDIOC;
fileExtension = ".sshw";
pickedBitMapping = true;
break;
default:
Console.WriteLine("Invalid selection, please try again.");
break;
}
}
break;
}
List<int> signalData = new List<int>();
int oddcheck = 0;
foreach (BitArray ba in debitted)
{
signalData.Add(0);
for (int i = 0; i < 256; i++) if (ba.Get(i)) if (bitMapping[i] != 0) signalData.Add(bitMapping[i]);
// 40 to 60 fps "interpolation"
if (oddcheck % 2 == 1)
{
signalData.Add(0);
for (int i = 0; i < 256; i++) if (ba.Get(i)) if (bitMapping[i] != 0) signalData.Add(bitMapping[i]);
}
oddcheck++;
}
rshwFormat rSHWOut = new rshwFormat();
rSHWOut.signalData = signalData.ToArray();
rSHWOut.audioData = File.ReadAllBytes("rshwin.wav");
rshwFormat.Save(filePath.Replace(".shw", fileExtension), rSHWOut);
CleanUpTempFiles();
succeededFiles++;
}
public static void ProgramBlue220Decode(string filePath)
{
byte[] fullFile = File.ReadAllBytes(filePath);
List<byte> outData = new List<byte>();
foreach (byte b in fullFile) outData.Add((byte)(b - 0x36));
File.WriteAllBytes("pbdecoded.tmp", outData.ToArray());
ProgramBlueGenericDecode();
}
public static void ProgramBlue471Decode(string filePath)
{
byte[] fullFile = File.ReadAllBytes(filePath);
List<byte> outData = new List<byte>();
long keyIndex = 0;
foreach (byte b in fullFile)
{
outData.Add((byte)(b - (ProgramBlueKey[keyIndex % 33])));
keyIndex++;
}
File.WriteAllBytes("pbdecoded.tmp", outData.ToArray());
ProgramBlueGenericDecode();
}
public static void ProgramBlue501Decode(string filePath, bool videoPresent)
{
string deviddedpath = filePath;
if (videoPresent)
{
byte[] vidding = File.ReadAllBytes(filePath);
int mp4PosMarker = vidding.Length - 1;
while(vidding[mp4PosMarker] != (byte)'#')
{
mp4PosMarker--;
}
mp4PosMarker++;
List<char> mp4IndexString = new List<char>();
for (int i = mp4PosMarker; i < vidding.Length; i++)
{
if (vidding[i] > 47 && vidding[i] < 58) mp4IndexString.Add((char)vidding[i]);
else break;
}
int mp4Index = int.Parse(new string(mp4IndexString.ToArray()));
List<byte> outVideoData = new List<byte>();
for (long l = 0; l < mp4Index; l++)
{
outVideoData.Add(vidding[l]);
}
File.WriteAllBytes(filePath.Replace(".shw", ".mp4"), outVideoData.ToArray());
List<byte> outDeViddedData = new List<byte>();
for (long l = mp4Index; l < vidding.Length; l++)
{
outDeViddedData.Add(vidding[l]);
}
File.WriteAllBytes("pbdecoded.tmp", outDeViddedData.ToArray());
deviddedpath = "pbdecoded.tmp";
}
string fileCheckAgainst = File.ReadAllText(deviddedpath);
long audioIndex = fileCheckAgainst.Split("<")[0].Length + 6;
long dataIndex = long.Parse(fileCheckAgainst.Split("<")[0]);
byte[] fullFile = File.ReadAllBytes(deviddedpath);
long dataEnd = fullFile.Length - 8;
List<byte> outAudioData = new List<byte>();
for (long l = audioIndex; l < dataIndex; l++)
{
outAudioData.Add(fullFile[l]);
}
while (outAudioData.Last() == 0)
{
outAudioData.RemoveAt(outAudioData.Count - 1);
}
File.WriteAllBytes("pboutaudio.tmp", outAudioData.ToArray());
while(fullFile[dataIndex] != 0x92)
{
dataIndex++;
}
List<byte> outData = new List<byte>();
long keyIndex = 0;
for (long l = dataIndex; l <= dataEnd; l++)
{
outData.Add((byte)(fullFile[l] - (ProgramBlueKey[keyIndex % 33])));
keyIndex++;
}
File.WriteAllBytes("pboutdata.xml", outData.ToArray());
}
public static void ProgramBlueGenericDecode()
{
byte[] fullFile = File.ReadAllBytes("pbdecoded.tmp");
int dataPosMarker = fullFile.Length - 1;
while(fullFile[dataPosMarker] != (byte)'#')
{
dataPosMarker--;
}
dataPosMarker++;
List<char> dataIndexString = new List<char>();
for (int i = dataPosMarker; i < fullFile.Length; i++)
{
if (fullFile[i] > 47 && fullFile[i] < 58) dataIndexString.Add((char)fullFile[i]);
else break;
}
int dataIndex = int.Parse(new string(dataIndexString.ToArray()));
List<byte> outAudioData = new List<byte>();
for (long l = 0; l < dataIndex; l++)
{
outAudioData.Add(fullFile[l]);
}
File.WriteAllBytes("pboutaudio.tmp", outAudioData.ToArray());
List<byte> outData = new List<byte>();
for (long l = dataIndex; l <= fullFile.Length - 1; l++) outData.Add(fullFile[l]);
File.WriteAllBytes("pboutdata.xml", outData.ToArray());
}
public static void RRToProgramBlue(string filePath)
{
string fileExtension = ".rshw";
int[] bitMapping = {};
string ctm = "";
bool pickedBitMapping = false;
while (!pickedBitMapping)
{
Console.WriteLine("Please specify a channel mapping:\nrae\nptt\nr12\nstudioc\n3st\nnrae\n");
Console.Write("> ");
string pick = Console.ReadLine();
switch (pick)
{
case "rae":
bitMapping = BitMappings.RRToProgramBlueBitMapping_RAE;
ctm = ChannelMappings.CTM_RAE;
fileExtension = ".rshw";
pickedBitMapping = true;
break;
case "ptt":
bitMapping = BitMappings.RRToProgramBlueBitMapping_CYBERS;
ctm = ChannelMappings.CTM_PTT;
fileExtension = ".cshw";
pickedBitMapping = true;
break;
case "3st":
bitMapping = BitMappings.RRToProgramBlueBitMapping_3ST;
ctm = ChannelMappings.CTM_3ST;
fileExtension = ".rshw";
pickedBitMapping = true;
break;
case "r12":
bitMapping = BitMappings.RRToProgramBlueBitMapping_CYBERS;
ctm = ChannelMappings.CTM_R12;
fileExtension = ".cshw";
pickedBitMapping = true;
break;
case "nrae":
bitMapping = BitMappings.RRToProgramBlueBitMapping_NRAE;
ctm = ChannelMappings.CTM_NRAE;
fileExtension = ".nshw";
pickedBitMapping = true;
break;
case "studioc":
bitMapping = BitMappings.RRToProgramBlueBitMapping_STUDIOC;
ctm = ChannelMappings.CTM_STUDIOC;
fileExtension = ".sshw";
pickedBitMapping = true;
break;
default:
Console.WriteLine("Invalid selection, please try again.");
break;
}
}
rshwFormat file = rshwFormat.Load(filePath);
if (file.signalData == null)
{
Console.WriteLine("File contains no signal data. Press any key to continue.");
Console.ReadKey();
failedFiles++;
return;
}
if (file.audioData == null)
{
Console.WriteLine("File contains no audio data. Press any key to continue.");
Console.ReadKey();
failedFiles++;
return;
}
List<BitArray> rshwBits = new List<BitArray>();
int countlength = 0;
if (file.signalData[0] != 0)
{
countlength = 1;
BitArray bit = new BitArray(300);
rshwBits.Add(bit);
}
for (int i = 0; i < file.signalData.Length; i++)
{
if (file.signalData[i] == 0)
{
countlength += 1;
BitArray bit = new BitArray(300);
rshwBits.Add(bit);
}
else
{
rshwBits[countlength - 1].Set(file.signalData[i], true);
}
}
List<string> writeOut = new List<string>();
int fIndex = 0;
foreach (BitArray bits in rshwBits)
{
fIndex++;
if (fIndex % 3 == 0) continue;
char[] frame = new char[256];
Array.Fill(frame, '0');
for (int i = 0; i < 300; i++) if (bitMapping[i] != 0) frame[bitMapping[i]-1] = bits.Get(i+1) ? '1' : '0';
writeOut.Add(new string(frame));
}
string xmlHeader = "\r\n<DSFROBOTSDATA>\r\n";
string versionFooter = "<v5.01>";
int dataIndex = file.audioData.Length + 0x7D;
string veryTopHeader = dataIndex.ToString() + "<dsfa>";
string xmlData = xmlHeader + String.Join("\r\n", writeOut.ToArray())+"\r\n</DSFROBOTSDATA>\r\n"+ctm+MakeFooter(Path.GetFileNameWithoutExtension(filePath), dataIndex);
UTF8Encoding utf8 = new UTF8Encoding();
byte[] writeOutFullData = utf8.GetBytes(veryTopHeader)
.Concat(file.audioData).ToArray()
.Concat(BlankArea).ToArray()
.Concat(ProgramBlue501Obfuscate(utf8.GetBytes(xmlData))).ToArray()
.Concat(utf8.GetBytes(versionFooter)).ToArray();
if (File.Exists(filePath.Replace(fileExtension, ".mp4")))
{
byte[] mp4File = File.ReadAllBytes(filePath.Replace(fileExtension, ".mp4"));
string mp4Footer = "mp4#"+mp4File.Length.ToString();
writeOutFullData = mp4File
.Concat(writeOutFullData).ToArray()
.Concat(utf8.GetBytes(mp4Footer)).ToArray();
}
File.WriteAllBytes(filePath.Replace(fileExtension, ".shw"),
writeOutFullData
);
succeededFiles++;
}
public static string MakeFooter(string name, int dataIndex)
{
return $"<DSFROBOTSPROJECTSETTINGS>\r\n" +
$"ProjectName={name.Replace(" ", "^")}\r\n" +
"FPS=40\r\n" +
"AudioExtension=wav\r\n" +
"AuthorName=Transmutate\r\n" +
"Revision=1\r\n" +
"Registration=\r\n" +
"Bouncer=\r\n" +
"ProgramTime=0\r\n" +
"Audiopeak=\r\n" +
"Audiochan=0\r\n" +
$"Created={DateTime.Now.ToString("MM/dd/yyyy^HH:mm:ss")}\r\n" +
"VideoOffset=0\r\n" +
"</DSFROBOTSPROJECTSETTINGS>\r\n" +
$"#{dataIndex.ToString()}";
}
public static byte[] ProgramBlue501Obfuscate(byte[] inData)
{
List<byte> outData = new List<byte>();
long keyIndex = 0;
foreach (byte b in inData)
{
outData.Add((byte)(b + (ProgramBlueKey[keyIndex % 33])));
keyIndex++;
}
return outData.ToArray();
}
public static void CleanUpTempFiles()
{
if (File.Exists("pboutaudio.tmp")) File.Delete("pboutaudio.tmp");
if (File.Exists("pboutdata.xml")) File.Delete("pboutdata.xml");
if (File.Exists("rshwin.wav")) File.Delete("rshwin.wav");
if (File.Exists("pbdecoded.tmp")) File.Delete("pbdecoded.tmp");
}
}
}