Guest User

cam4cmdline

a guest
Jun 18th, 2015
6,785
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 17.80 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Net.Sockets;
  8. using System.Runtime.InteropServices;
  9. using System.Text;
  10. using System.Text.RegularExpressions;
  11.  
  12. /*
  13.     Command line tool to download models from Cam4.
  14.     Requires librtmp.dll from https://github.com/K-S-V/Scripts/releases
  15.     Some quick dirty code, no AMF parser or anything, just Regex. LOL
  16.     Cam4 is a shitty webcam site anyway, doesn't deserve any better.
  17. */
  18.  
  19. namespace cam4cmdline
  20. {
  21.     class Program
  22.     {
  23.         [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
  24.         public extern static IntPtr RTMP_Alloc();
  25.         [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
  26.         public extern static void RTMP_Init(IntPtr rtmp);
  27.         [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
  28.         public extern static int RTMP_SetupURL(IntPtr rtmp, IntPtr url);
  29.         [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
  30.         public extern static int RTMP_Connect(IntPtr rtmp, IntPtr cp);
  31.         [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
  32.         public extern static int RTMP_ConnectStream(IntPtr rtmp, int seekTime);
  33.         [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
  34.         public extern static int RTMP_Read(IntPtr rtmp, byte[] buffer, int size);
  35.         [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
  36.         public extern static void RTMP_Free(IntPtr rtmp);
  37.         [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
  38.         public extern static void RTMP_Close(IntPtr rtmp);
  39.  
  40.         private const int RTMP_TYPE_COMMAND_AMF3    = 0x11;
  41.         private const int RTMP_TYPE_COMMAND_AMF0    = 0x14;
  42.  
  43.         private static Random rnd                   = new Random();
  44.         private static string chatServerUrl         = string.Empty;
  45.         private static string chatServerIP          = string.Empty;
  46.         private static string chatServerApp         = string.Empty;
  47.         private static string modelName             = string.Empty;
  48.         private static string rtmpUrl               = string.Empty;
  49.         private static string rtmpUrlPlaypath       = string.Empty;
  50.  
  51.         static void Main(string[] args)
  52.         {
  53.             SetHeader(false);
  54.             GetModelName();
  55.             GetChatServerUrl();
  56.             GetVideoStreamUrl();
  57.             DownloadModel();
  58.         }
  59.  
  60.         private static void GetModelName()
  61.         {
  62.             Console.Write("\r\nEnter model name: ");
  63.             modelName = Console.ReadLine().Trim().ToLower();
  64.             Match m = Regex.Match(modelName, "^[a-z0-9_]{4,12}$");
  65.             if (!m.Success)
  66.             {
  67.                 LogExit("Invalid model name entered. Model name must be between 4-12 chars and can only contain a-z0-9_");
  68.             }
  69.             SetHeader(true);
  70.         }
  71.  
  72.         private static void GetChatServerUrl()
  73.         {
  74.             LogInfo("\r\nRequesting chat server url:\r\n * Connecting to Cam4.");
  75.             string result = string.Empty;
  76.             try
  77.             {
  78.                 using (WebClient wc = new WebClient())
  79.                 {
  80.                     result = wc.DownloadString(string.Format("http://cam4.com/direct?username=guest&room={0}&devenv=true&rand={1}", modelName, rnd.Next(1111111, 5555555)));
  81.                 }
  82.             }
  83.             catch (Exception ex)
  84.             {
  85.                 LogExit(ex.Message);
  86.             }
  87.             if (result.StartsWith("rtmp://") && (result.Contains("/cam4-cr")))
  88.             {
  89.                 Uri url = new Uri(result);
  90.                 chatServerUrl = result;
  91.                 chatServerIP = url.Host;
  92.                 chatServerApp = url.Segments.Last();
  93.                 LogInfo(string.Format(" * Chat server url: {0}", chatServerUrl));
  94.             }
  95.             else
  96.             {
  97.                 LogExit(result);
  98.             }
  99.         }
  100.  
  101.         private static void GetVideoStreamUrl()
  102.         {
  103.             LogInfo("\r\nRequesting video stream url:");
  104.             try
  105.             {
  106.                 using (TcpClient client = new TcpClient() { SendTimeout = 10000, ReceiveTimeout = 10000 })
  107.                 {
  108.                     client.Connect(chatServerIP, 1935);
  109.                     using (BE_BinaryReader reader = new BE_BinaryReader(client.GetStream()))
  110.                     {
  111.                         using (BinaryWriter writer = new BinaryWriter(client.GetStream()))
  112.                         {
  113.                             DoHandshake(reader, writer);
  114.                             SendConnect(writer);
  115.                             ReadPackets(reader, writer);
  116.                         }
  117.                     }
  118.                 }
  119.             }
  120.             catch (Exception ex)
  121.             {
  122.                 LogExit(ex.Message);
  123.             }
  124.         }
  125.  
  126.         private static void DownloadModel()
  127.         {
  128.             LogInfo("\r\nStarting video stream download:\r\n * Connecting to video stream server (might take a few seconds).");
  129.  
  130.             int bytesRead = 0;
  131.             long totalDuration = 0;
  132.             byte[] buffer = new byte[32768];
  133.             Stopwatch duration = new Stopwatch();
  134.  
  135.             IntPtr r = RTMP_Alloc();
  136.             if (r == IntPtr.Zero)
  137.                 LogExit("Failed to create session handle.");
  138.             RTMP_Init(r);
  139.             RTMP_SetupURL(r, Marshal.StringToHGlobalAnsi(String.Format("{0} playpath={1}", rtmpUrl, rtmpUrlPlaypath)));
  140.             if (RTMP_Connect(r, IntPtr.Zero) == 0)
  141.                 LogExit("Failed to establish RTMP connection.");
  142.             if (RTMP_ConnectStream(r, 0) == 0)
  143.                 LogExit("Failed to establish RTMP session.");
  144.  
  145.             duration.Start();
  146.  
  147.             try
  148.             {
  149.                 string path = GetUniqueFilename();
  150.                 Directory.CreateDirectory(Path.GetDirectoryName(path));
  151.                 LogInfo(string.Format(" * Saving as: {0}\r\n", Path.GetFileName(path)));
  152.  
  153.                 using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 1048576))
  154.                 {
  155.                     do
  156.                     {
  157.                         bytesRead = RTMP_Read(r, buffer, buffer.Length);
  158.                         if (bytesRead == 0) break;
  159.                         fs.Write(buffer, 0, bytesRead);
  160.                         if ((duration.ElapsedMilliseconds - totalDuration) >= 1000)
  161.                         {
  162.                             totalDuration = duration.ElapsedMilliseconds;
  163.                             Console.Write("\r{0} / {1}", FormatSize(fs.Position), FormatDuration(totalDuration));
  164.                         }
  165.                     } while (true);
  166.                 }
  167.             }
  168.             catch (Exception ex)
  169.             {
  170.                 LogExit(ex.Message);
  171.             }
  172.             finally
  173.             {
  174.                 RTMP_Close(r);
  175.                 RTMP_Free(r);
  176.             }
  177.         }
  178.  
  179.  
  180.         private static void DoHandshake(BinaryReader reader, BinaryWriter writer)
  181.         {
  182.             LogInfo(" * RTMP Handshake.");
  183.             // C0
  184.             byte protocolVersion = 0x03;
  185.             writer.Write(protocolVersion);
  186.             // C1
  187.             byte[] randomData = new byte[1536]; // 1536 random bytes, server doesn't care.
  188.             rnd.NextBytes(randomData);
  189.             writer.Write(randomData);
  190.             // S0
  191.             if (reader.ReadByte() != protocolVersion)
  192.                 LogExit("Server sent wrong protocol version.");
  193.             // S1
  194.             byte[] receivedS1 = reader.ReadBytes(1536); // Server always sends the exact same 1536 bytes.
  195.             // S2
  196.             reader.ReadBytes(1536);
  197.             // C2
  198.             writer.Write(receivedS1);
  199.         }
  200.  
  201.         private static void ReadPackets(BE_BinaryReader reader, BinaryWriter writer)
  202.         {
  203.             while (true)
  204.             {
  205.                 int chunkId = reader.ReadByte();
  206.                 int timeStamp = reader.BE_ReadInt24();
  207.                 int bodySize = reader.BE_ReadInt24();
  208.                 int typeId = reader.ReadByte();
  209.                 int streamId = reader.ReadInt32();
  210.                 byte[] body = reader.ReadBytes(bodySize);
  211.  
  212.                 if (typeId == RTMP_TYPE_COMMAND_AMF3)
  213.                 {
  214.                     bool result = ExtractRTMPUrl(body);
  215.                     if (result) return;
  216.                 }
  217.                 else if (typeId == RTMP_TYPE_COMMAND_AMF0)
  218.                 {
  219.                     SendInitConnect(writer);
  220.                     SendRequestVideoStream(writer);
  221.                 }
  222.             }
  223.         }
  224.  
  225.         private static void SendConnect(BinaryWriter writer)
  226.         {
  227.             LogInfo(" * Sending Connect packet.");
  228.             List<byte> enc = new List<byte>();
  229.             EncodeString(enc, "connect");
  230.             EncodeNumber(enc, 1.0);
  231.             enc.Add(0x03);                
  232.             EncodeString(enc, "app", chatServerApp);
  233.             EncodeString(enc, "flashVer", "WIN 18,0,0,160");
  234.             EncodeString(enc, "swfUrl", "http://edgecast.cam4s.com/client/Cam4_6.265_guest.swf");
  235.             EncodeString(enc, "tcUrl", chatServerUrl);
  236.             EncodeBoolean(enc, "fpad", false);
  237.             EncodeNumber(enc, "capabilities", 239.0);
  238.             EncodeNumber(enc, "audioCodecs", 3575.0);
  239.             EncodeNumber(enc, "videoCodecs", 252.0);
  240.             EncodeNumber(enc, "videoFunction", 1.0);
  241.             EncodeString(enc, "pageUrl", string.Format("http://www.cam4.com/{0}", modelName));
  242.             EncodeNumber(enc, "objectEncoding", 3.0);
  243.             enc.Add(0);
  244.             enc.Add(0);
  245.             enc.Add(0x09);
  246.             EncodeString(enc, modelName);
  247.             EncodeString(enc, "guest");
  248.             EncodeString(enc, "");
  249.             SendMessage(writer, enc);
  250.         }
  251.  
  252.         private static void SendInitConnect(BinaryWriter writer)
  253.         {
  254.             LogInfo(" * Sending initConnect packet.");
  255.             List<byte> enc = new List<byte>();
  256.             EncodeString(enc, "initConnect");
  257.             EncodeNumber(enc, 0.0);
  258.             enc.Add(0x05);
  259.             EncodeString(enc, modelName);
  260.             EncodeString(enc, "guest");
  261.             EncodeString(enc, "");
  262.             SendMessage(writer, enc);
  263.         }
  264.  
  265.         private static void SendRequestVideoStream(BinaryWriter writer)
  266.         {
  267.             LogInfo(" * Sending requestVideoStream packet.");
  268.             List<byte> enc = new List<byte>();
  269.             EncodeString(enc, "requestVideoStream");
  270.             EncodeNumber(enc, 2.0);
  271.             enc.Add(0x05);
  272.             enc.Add(0x05);
  273.             SendMessage(writer, enc);
  274.         }
  275.  
  276.         private static void SendMessage(BinaryWriter writer, List<byte> data)
  277.         {
  278.             byte chunkHeaderType = 0x03;
  279.             byte[] timeStampDelta = new byte[] { 0x00, 0x00, 0x00 };
  280.             byte[] packetLengthBytes = EncodeInt24(data.Count);
  281.             byte messageType = 0x14; // AMF0
  282.             byte[] streamIdBytes = new byte[] { 0x00, 0x00, 0x00, 0x00 };
  283.  
  284.             // Add byteHeader every 128 bytes
  285.             byte byteHeader = 0xc3;
  286.             int headerPos = 128;
  287.             while (!(headerPos >= data.Count))
  288.             {
  289.                 data.Insert(headerPos, byteHeader);
  290.                 headerPos += 129;
  291.             }
  292.             using (MemoryStream ms = new MemoryStream())
  293.             {
  294.                 ms.WriteByte(chunkHeaderType);
  295.                 ms.Write(timeStampDelta, 0, timeStampDelta.Length);
  296.                 ms.Write(packetLengthBytes, 0, packetLengthBytes.Length);
  297.                 ms.WriteByte(messageType);
  298.                 ms.Write(streamIdBytes, 0, streamIdBytes.Length);
  299.                 ms.Write(data.ToArray(), 0, data.Count);
  300.                 writer.Write(ms.ToArray(), 0, (int)ms.Length);
  301.             }
  302.         }
  303.  
  304.         private static void EncodeString(List<byte> output, string name, string value)
  305.         {
  306.             short length = IPAddress.HostToNetworkOrder((short)name.Length);
  307.             output.AddRange(BitConverter.GetBytes(length));
  308.             output.AddRange(Encoding.ASCII.GetBytes(name));
  309.             EncodeString(output, value);
  310.         }
  311.  
  312.         private static void EncodeString(List<byte> output, string value)
  313.         {
  314.             output.Add(0x02); // String
  315.             short length = IPAddress.HostToNetworkOrder((short)value.Length);
  316.             output.AddRange(BitConverter.GetBytes(length));
  317.             output.AddRange(Encoding.ASCII.GetBytes(value));
  318.         }
  319.  
  320.         private static void EncodeBoolean(List<byte> output, string name, bool value)
  321.         {
  322.             short length = IPAddress.HostToNetworkOrder((short)name.Length);
  323.             output.AddRange(BitConverter.GetBytes(length));
  324.             output.AddRange(Encoding.ASCII.GetBytes(name));
  325.             EncodeBoolean(output, value);
  326.         }
  327.  
  328.         private static void EncodeBoolean(List<byte> output, bool value)
  329.         {
  330.             output.Add(0x01); // Boolean
  331.             output.Add(value ? (byte)0x01 : (byte)0x00);
  332.         }
  333.  
  334.         private static void EncodeNumber(List<byte> output, string name, double value)
  335.         {
  336.             short length = IPAddress.HostToNetworkOrder((short)name.Length);
  337.             output.AddRange(BitConverter.GetBytes(length));
  338.             output.AddRange(Encoding.ASCII.GetBytes(name));
  339.             EncodeNumber(output, value);
  340.         }
  341.  
  342.         private static void EncodeNumber(List<byte> output, double value)
  343.         {
  344.             output.Add(0x00); // Number
  345.             byte[] bytes = BitConverter.GetBytes(value);
  346.             Array.Reverse(bytes);
  347.             output.AddRange(bytes);
  348.         }
  349.  
  350.         private static byte[] EncodeInt24(int nVal)
  351.         {
  352.             byte[] bytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(nVal));
  353.             return bytes.Skip(1).Take(3).ToArray();
  354.         }
  355.  
  356.         private static string FormatSize(long bytes)
  357.         {
  358.             return string.Format("{0:n0} kB", (bytes / 1024));
  359.         }
  360.  
  361.         private static string FormatDuration(long ms)
  362.         {
  363.             TimeSpan t = TimeSpan.FromMilliseconds(ms);
  364.             string result = string.Format("{0:D2}:{1:D2}:{2:D2}", t.Hours, t.Minutes, t.Seconds);
  365.             return result;
  366.         }
  367.  
  368.         // Very dirty hack (LOL). I couldn't be bothered to use a proper AMF parser, just to get the rtmp url.
  369.         private static bool ExtractRTMPUrl(byte[] data)
  370.         {
  371.             // Only keep printable ascii characters.
  372.             StringBuilder sb = new StringBuilder();
  373.             for (int i = 0; i < data.Length; i++)
  374.                 if (data[i] >= 32 && data[i] <= 126)
  375.                     sb.Append(Convert.ToChar(data[i]));
  376.  
  377.             string result = sb.ToString();
  378.             Match m = Regex.Match(result, @"rtmp://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/cam4-origin\d{1,3}/_definst_/(streams/[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}\?[a-z0-9]{32})");
  379.             if (m.Success)
  380.             {
  381.                 rtmpUrl = m.Value;
  382.                 rtmpUrlPlaypath = m.Groups[1].Value;
  383.                 LogInfo(string.Format(" * Video stream url: {0}", rtmpUrl));
  384.                 return true;
  385.             }
  386.             return false;
  387.         }
  388.  
  389.         public static string GetUniqueFilename()
  390.         {
  391.             string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Cam4", modelName);
  392.             string name = string.Format("{0}_({1})", modelName, DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"));
  393.             if (!(File.Exists(Path.Combine(path, name + ".flv"))))
  394.             {
  395.                 return Path.Combine(path, name + ".flv");
  396.             }
  397.             int i = 0;
  398.             while (File.Exists(Path.Combine(path, string.Format(name + "_{0}", i) + ".flv")))
  399.             {
  400.                 i++;
  401.             }
  402.             return Path.Combine(path, string.Format(name + "_{0}", i) + ".flv");
  403.         }
  404.  
  405.         private static void SetHeader(bool showModelName)
  406.         {
  407.             if (showModelName)
  408.             {
  409.                 Console.Title = string.Format("cam4cmdline - {0}", modelName);
  410.                 Console.CursorVisible = false;
  411.             }
  412.             else
  413.             {
  414.                 Console.Title = "cam4cmdline";
  415.                 string header = ".:: cam4cmdline by Elgero ::.";
  416.                 Console.SetCursorPosition((Console.WindowWidth - header.Length) / 2, Console.CursorTop);
  417.                 LogInfo(header);
  418.             }
  419.         }
  420.  
  421.         private static void LogInfo(string message)
  422.         {
  423.             Console.WriteLine(message);
  424.         }
  425.  
  426.         private static void LogExit(string message)
  427.         {
  428.             Console.ForegroundColor = ConsoleColor.Red;
  429.             switch (message)
  430.             {
  431.                 case "not_broadcasting":
  432.                     LogInfo("This model is currently not broadcasting.");
  433.                     break;
  434.                 case "invalid_broadcaster":
  435.                     LogInfo("This model does not exist.");
  436.                     break;
  437.                 default:
  438.                     LogInfo(message);
  439.                     break;
  440.             }
  441.             Environment.Exit(1);
  442.         }
  443.  
  444.         private class BE_BinaryReader : BinaryReader
  445.         {
  446.             public BE_BinaryReader(Stream stream, Encoding encoding) : base(stream, encoding)
  447.             {
  448.             }
  449.             public BE_BinaryReader(Stream stream) : base(stream)
  450.             {
  451.             }
  452.             public int BE_ReadInt24()
  453.             {
  454.                 byte[] data = base.ReadBytes(3);
  455.                 int d = data[0] * 65536 + data[1] * 256 + data[2];
  456.                 return d;
  457.             }
  458.         }
  459.     }
  460. }
Advertisement
Add Comment
Please, Sign In to add comment