Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.IO;
- using System.Linq;
- using System.Net;
- using System.Net.Sockets;
- using System.Runtime.InteropServices;
- using System.Text;
- using System.Text.RegularExpressions;
- /*
- Command line tool to download models from Cam4.
- Requires librtmp.dll from https://github.com/K-S-V/Scripts/releases
- Some quick dirty code, no AMF parser or anything, just Regex. LOL
- Cam4 is a shitty webcam site anyway, doesn't deserve any better.
- */
- namespace cam4cmdline
- {
- class Program
- {
- [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
- public extern static IntPtr RTMP_Alloc();
- [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
- public extern static void RTMP_Init(IntPtr rtmp);
- [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
- public extern static int RTMP_SetupURL(IntPtr rtmp, IntPtr url);
- [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
- public extern static int RTMP_Connect(IntPtr rtmp, IntPtr cp);
- [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
- public extern static int RTMP_ConnectStream(IntPtr rtmp, int seekTime);
- [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
- public extern static int RTMP_Read(IntPtr rtmp, byte[] buffer, int size);
- [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
- public extern static void RTMP_Free(IntPtr rtmp);
- [DllImport("librtmp.dll", CallingConvention = CallingConvention.Cdecl)]
- public extern static void RTMP_Close(IntPtr rtmp);
- private const int RTMP_TYPE_COMMAND_AMF3 = 0x11;
- private const int RTMP_TYPE_COMMAND_AMF0 = 0x14;
- private static Random rnd = new Random();
- private static string chatServerUrl = string.Empty;
- private static string chatServerIP = string.Empty;
- private static string chatServerApp = string.Empty;
- private static string modelName = string.Empty;
- private static string rtmpUrl = string.Empty;
- private static string rtmpUrlPlaypath = string.Empty;
- static void Main(string[] args)
- {
- SetHeader(false);
- GetModelName();
- GetChatServerUrl();
- GetVideoStreamUrl();
- DownloadModel();
- }
- private static void GetModelName()
- {
- Console.Write("\r\nEnter model name: ");
- modelName = Console.ReadLine().Trim().ToLower();
- Match m = Regex.Match(modelName, "^[a-z0-9_]{4,12}$");
- if (!m.Success)
- {
- LogExit("Invalid model name entered. Model name must be between 4-12 chars and can only contain a-z0-9_");
- }
- SetHeader(true);
- }
- private static void GetChatServerUrl()
- {
- LogInfo("\r\nRequesting chat server url:\r\n * Connecting to Cam4.");
- string result = string.Empty;
- try
- {
- using (WebClient wc = new WebClient())
- {
- result = wc.DownloadString(string.Format("http://cam4.com/direct?username=guest&room={0}&devenv=true&rand={1}", modelName, rnd.Next(1111111, 5555555)));
- }
- }
- catch (Exception ex)
- {
- LogExit(ex.Message);
- }
- if (result.StartsWith("rtmp://") && (result.Contains("/cam4-cr")))
- {
- Uri url = new Uri(result);
- chatServerUrl = result;
- chatServerIP = url.Host;
- chatServerApp = url.Segments.Last();
- LogInfo(string.Format(" * Chat server url: {0}", chatServerUrl));
- }
- else
- {
- LogExit(result);
- }
- }
- private static void GetVideoStreamUrl()
- {
- LogInfo("\r\nRequesting video stream url:");
- try
- {
- using (TcpClient client = new TcpClient() { SendTimeout = 10000, ReceiveTimeout = 10000 })
- {
- client.Connect(chatServerIP, 1935);
- using (BE_BinaryReader reader = new BE_BinaryReader(client.GetStream()))
- {
- using (BinaryWriter writer = new BinaryWriter(client.GetStream()))
- {
- DoHandshake(reader, writer);
- SendConnect(writer);
- ReadPackets(reader, writer);
- }
- }
- }
- }
- catch (Exception ex)
- {
- LogExit(ex.Message);
- }
- }
- private static void DownloadModel()
- {
- LogInfo("\r\nStarting video stream download:\r\n * Connecting to video stream server (might take a few seconds).");
- int bytesRead = 0;
- long totalDuration = 0;
- byte[] buffer = new byte[32768];
- Stopwatch duration = new Stopwatch();
- IntPtr r = RTMP_Alloc();
- if (r == IntPtr.Zero)
- LogExit("Failed to create session handle.");
- RTMP_Init(r);
- RTMP_SetupURL(r, Marshal.StringToHGlobalAnsi(String.Format("{0} playpath={1}", rtmpUrl, rtmpUrlPlaypath)));
- if (RTMP_Connect(r, IntPtr.Zero) == 0)
- LogExit("Failed to establish RTMP connection.");
- if (RTMP_ConnectStream(r, 0) == 0)
- LogExit("Failed to establish RTMP session.");
- duration.Start();
- try
- {
- string path = GetUniqueFilename();
- Directory.CreateDirectory(Path.GetDirectoryName(path));
- LogInfo(string.Format(" * Saving as: {0}\r\n", Path.GetFileName(path)));
- using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 1048576))
- {
- do
- {
- bytesRead = RTMP_Read(r, buffer, buffer.Length);
- if (bytesRead == 0) break;
- fs.Write(buffer, 0, bytesRead);
- if ((duration.ElapsedMilliseconds - totalDuration) >= 1000)
- {
- totalDuration = duration.ElapsedMilliseconds;
- Console.Write("\r{0} / {1}", FormatSize(fs.Position), FormatDuration(totalDuration));
- }
- } while (true);
- }
- }
- catch (Exception ex)
- {
- LogExit(ex.Message);
- }
- finally
- {
- RTMP_Close(r);
- RTMP_Free(r);
- }
- }
- private static void DoHandshake(BinaryReader reader, BinaryWriter writer)
- {
- LogInfo(" * RTMP Handshake.");
- // C0
- byte protocolVersion = 0x03;
- writer.Write(protocolVersion);
- // C1
- byte[] randomData = new byte[1536]; // 1536 random bytes, server doesn't care.
- rnd.NextBytes(randomData);
- writer.Write(randomData);
- // S0
- if (reader.ReadByte() != protocolVersion)
- LogExit("Server sent wrong protocol version.");
- // S1
- byte[] receivedS1 = reader.ReadBytes(1536); // Server always sends the exact same 1536 bytes.
- // S2
- reader.ReadBytes(1536);
- // C2
- writer.Write(receivedS1);
- }
- private static void ReadPackets(BE_BinaryReader reader, BinaryWriter writer)
- {
- while (true)
- {
- int chunkId = reader.ReadByte();
- int timeStamp = reader.BE_ReadInt24();
- int bodySize = reader.BE_ReadInt24();
- int typeId = reader.ReadByte();
- int streamId = reader.ReadInt32();
- byte[] body = reader.ReadBytes(bodySize);
- if (typeId == RTMP_TYPE_COMMAND_AMF3)
- {
- bool result = ExtractRTMPUrl(body);
- if (result) return;
- }
- else if (typeId == RTMP_TYPE_COMMAND_AMF0)
- {
- SendInitConnect(writer);
- SendRequestVideoStream(writer);
- }
- }
- }
- private static void SendConnect(BinaryWriter writer)
- {
- LogInfo(" * Sending Connect packet.");
- List<byte> enc = new List<byte>();
- EncodeString(enc, "connect");
- EncodeNumber(enc, 1.0);
- enc.Add(0x03);
- EncodeString(enc, "app", chatServerApp);
- EncodeString(enc, "flashVer", "WIN 18,0,0,160");
- EncodeString(enc, "swfUrl", "http://edgecast.cam4s.com/client/Cam4_6.265_guest.swf");
- EncodeString(enc, "tcUrl", chatServerUrl);
- EncodeBoolean(enc, "fpad", false);
- EncodeNumber(enc, "capabilities", 239.0);
- EncodeNumber(enc, "audioCodecs", 3575.0);
- EncodeNumber(enc, "videoCodecs", 252.0);
- EncodeNumber(enc, "videoFunction", 1.0);
- EncodeString(enc, "pageUrl", string.Format("http://www.cam4.com/{0}", modelName));
- EncodeNumber(enc, "objectEncoding", 3.0);
- enc.Add(0);
- enc.Add(0);
- enc.Add(0x09);
- EncodeString(enc, modelName);
- EncodeString(enc, "guest");
- EncodeString(enc, "");
- SendMessage(writer, enc);
- }
- private static void SendInitConnect(BinaryWriter writer)
- {
- LogInfo(" * Sending initConnect packet.");
- List<byte> enc = new List<byte>();
- EncodeString(enc, "initConnect");
- EncodeNumber(enc, 0.0);
- enc.Add(0x05);
- EncodeString(enc, modelName);
- EncodeString(enc, "guest");
- EncodeString(enc, "");
- SendMessage(writer, enc);
- }
- private static void SendRequestVideoStream(BinaryWriter writer)
- {
- LogInfo(" * Sending requestVideoStream packet.");
- List<byte> enc = new List<byte>();
- EncodeString(enc, "requestVideoStream");
- EncodeNumber(enc, 2.0);
- enc.Add(0x05);
- enc.Add(0x05);
- SendMessage(writer, enc);
- }
- private static void SendMessage(BinaryWriter writer, List<byte> data)
- {
- byte chunkHeaderType = 0x03;
- byte[] timeStampDelta = new byte[] { 0x00, 0x00, 0x00 };
- byte[] packetLengthBytes = EncodeInt24(data.Count);
- byte messageType = 0x14; // AMF0
- byte[] streamIdBytes = new byte[] { 0x00, 0x00, 0x00, 0x00 };
- // Add byteHeader every 128 bytes
- byte byteHeader = 0xc3;
- int headerPos = 128;
- while (!(headerPos >= data.Count))
- {
- data.Insert(headerPos, byteHeader);
- headerPos += 129;
- }
- using (MemoryStream ms = new MemoryStream())
- {
- ms.WriteByte(chunkHeaderType);
- ms.Write(timeStampDelta, 0, timeStampDelta.Length);
- ms.Write(packetLengthBytes, 0, packetLengthBytes.Length);
- ms.WriteByte(messageType);
- ms.Write(streamIdBytes, 0, streamIdBytes.Length);
- ms.Write(data.ToArray(), 0, data.Count);
- writer.Write(ms.ToArray(), 0, (int)ms.Length);
- }
- }
- private static void EncodeString(List<byte> output, string name, string value)
- {
- short length = IPAddress.HostToNetworkOrder((short)name.Length);
- output.AddRange(BitConverter.GetBytes(length));
- output.AddRange(Encoding.ASCII.GetBytes(name));
- EncodeString(output, value);
- }
- private static void EncodeString(List<byte> output, string value)
- {
- output.Add(0x02); // String
- short length = IPAddress.HostToNetworkOrder((short)value.Length);
- output.AddRange(BitConverter.GetBytes(length));
- output.AddRange(Encoding.ASCII.GetBytes(value));
- }
- private static void EncodeBoolean(List<byte> output, string name, bool value)
- {
- short length = IPAddress.HostToNetworkOrder((short)name.Length);
- output.AddRange(BitConverter.GetBytes(length));
- output.AddRange(Encoding.ASCII.GetBytes(name));
- EncodeBoolean(output, value);
- }
- private static void EncodeBoolean(List<byte> output, bool value)
- {
- output.Add(0x01); // Boolean
- output.Add(value ? (byte)0x01 : (byte)0x00);
- }
- private static void EncodeNumber(List<byte> output, string name, double value)
- {
- short length = IPAddress.HostToNetworkOrder((short)name.Length);
- output.AddRange(BitConverter.GetBytes(length));
- output.AddRange(Encoding.ASCII.GetBytes(name));
- EncodeNumber(output, value);
- }
- private static void EncodeNumber(List<byte> output, double value)
- {
- output.Add(0x00); // Number
- byte[] bytes = BitConverter.GetBytes(value);
- Array.Reverse(bytes);
- output.AddRange(bytes);
- }
- private static byte[] EncodeInt24(int nVal)
- {
- byte[] bytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(nVal));
- return bytes.Skip(1).Take(3).ToArray();
- }
- private static string FormatSize(long bytes)
- {
- return string.Format("{0:n0} kB", (bytes / 1024));
- }
- private static string FormatDuration(long ms)
- {
- TimeSpan t = TimeSpan.FromMilliseconds(ms);
- string result = string.Format("{0:D2}:{1:D2}:{2:D2}", t.Hours, t.Minutes, t.Seconds);
- return result;
- }
- // Very dirty hack (LOL). I couldn't be bothered to use a proper AMF parser, just to get the rtmp url.
- private static bool ExtractRTMPUrl(byte[] data)
- {
- // Only keep printable ascii characters.
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < data.Length; i++)
- if (data[i] >= 32 && data[i] <= 126)
- sb.Append(Convert.ToChar(data[i]));
- string result = sb.ToString();
- 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})");
- if (m.Success)
- {
- rtmpUrl = m.Value;
- rtmpUrlPlaypath = m.Groups[1].Value;
- LogInfo(string.Format(" * Video stream url: {0}", rtmpUrl));
- return true;
- }
- return false;
- }
- public static string GetUniqueFilename()
- {
- string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Cam4", modelName);
- string name = string.Format("{0}_({1})", modelName, DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"));
- if (!(File.Exists(Path.Combine(path, name + ".flv"))))
- {
- return Path.Combine(path, name + ".flv");
- }
- int i = 0;
- while (File.Exists(Path.Combine(path, string.Format(name + "_{0}", i) + ".flv")))
- {
- i++;
- }
- return Path.Combine(path, string.Format(name + "_{0}", i) + ".flv");
- }
- private static void SetHeader(bool showModelName)
- {
- if (showModelName)
- {
- Console.Title = string.Format("cam4cmdline - {0}", modelName);
- Console.CursorVisible = false;
- }
- else
- {
- Console.Title = "cam4cmdline";
- string header = ".:: cam4cmdline by Elgero ::.";
- Console.SetCursorPosition((Console.WindowWidth - header.Length) / 2, Console.CursorTop);
- LogInfo(header);
- }
- }
- private static void LogInfo(string message)
- {
- Console.WriteLine(message);
- }
- private static void LogExit(string message)
- {
- Console.ForegroundColor = ConsoleColor.Red;
- switch (message)
- {
- case "not_broadcasting":
- LogInfo("This model is currently not broadcasting.");
- break;
- case "invalid_broadcaster":
- LogInfo("This model does not exist.");
- break;
- default:
- LogInfo(message);
- break;
- }
- Environment.Exit(1);
- }
- private class BE_BinaryReader : BinaryReader
- {
- public BE_BinaryReader(Stream stream, Encoding encoding) : base(stream, encoding)
- {
- }
- public BE_BinaryReader(Stream stream) : base(stream)
- {
- }
- public int BE_ReadInt24()
- {
- byte[] data = base.ReadBytes(3);
- int d = data[0] * 65536 + data[1] * 256 + data[2];
- return d;
- }
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment