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 outData = new List(); 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 outData = new List(); UTF8Encoding utf8 = new UTF8Encoding(); List audioData = new List(File.ReadAllBytes("pboutaudio.tmp")); while (audioData.Last() == 0) { audioData.RemoveAt(audioData.Count - 1); } string versionFooter = ""; int dataIndex = audioData.Count + 0x7D; string veryTopHeader = dataIndex.ToString() + ""; 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 debitted = new List(); int frame = 0; foreach (string line in pbXmlData) { //Console.WriteLine(line); if (line.Trim() == "") { readingDataFlag = true; continue; } if (line.Trim() == "") { readingDataFlag = false; continue; } if (line.Trim() == "") { 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 signalData = new List(); 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 outData = new List(); 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 outData = new List(); 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 mp4IndexString = new List(); 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 outVideoData = new List(); for (long l = 0; l < mp4Index; l++) { outVideoData.Add(vidding[l]); } File.WriteAllBytes(filePath.Replace(".shw", ".mp4"), outVideoData.ToArray()); List outDeViddedData = new List(); 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 outAudioData = new List(); 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 outData = new List(); 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 dataIndexString = new List(); 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 outAudioData = new List(); for (long l = 0; l < dataIndex; l++) { outAudioData.Add(fullFile[l]); } File.WriteAllBytes("pboutaudio.tmp", outAudioData.ToArray()); List outData = new List(); 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 rshwBits = new List(); 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 writeOut = new List(); 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\r\n"; string versionFooter = ""; int dataIndex = file.audioData.Length + 0x7D; string veryTopHeader = dataIndex.ToString() + ""; string xmlData = xmlHeader + String.Join("\r\n", writeOut.ToArray())+"\r\n\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 $"\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" + "\r\n" + $"#{dataIndex.ToString()}"; } public static byte[] ProgramBlue501Obfuscate(byte[] inData) { List outData = new List(); 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"); } } }