$ git clone http://tcclient.ion.nu/tc_client.git
commit 00f823523b6565fece30ecc8d60d8ebb7e7bbb96
Author: Alicia <...>
Date:   Tue Apr 7 06:49:01 2015 +0200

    Version 0.12

diff --git a/ChangeLog b/ChangeLog
index 93f7f26..f2b31d1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+0.12:
+Wrote a sender for the RTMP code and changed the AMF0 writer to use it, plus working directly with the rtmp struct instead of an intermediate amfmsg struct.
+Added handling of moderators turning into regular users, or 'deop' (contributed by Jade)
+Handle new nicknames being in use when using /nick, and thus failing (contributed by Jade)
 0.11:
 Rewrote the RTMP code to read from the socket instead of from a buffer, this will prevent messages from being dropped when they are larger than the buffer.
 0.10:
diff --git a/Makefile b/Makefile
index 71d3434..a96e224 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=0.11
+VERSION=0.12
 CFLAGS=-g3 -Wall $(shell curl-config --cflags)
 LIBS=-g3 $(shell curl-config --libs)
 
diff --git a/amfwriter.c b/amfwriter.c
index 2011c8f..dd51863 100644
--- a/amfwriter.c
+++ b/amfwriter.c
@@ -1,6 +1,6 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
-    Copyright (C) 2014  alicia@ion.nu
+    Copyright (C) 2014-2015  alicia@ion.nu
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -19,87 +19,43 @@
 #include <stdint.h>
 #include <unistd.h>
 #include <endian.h>
+#include "rtmp.h"
 #include "amfwriter.h"
 
-#pragma pack(push)
-#pragma pack(1)
-struct rtmph
+void amfinit(struct rtmp* msg, unsigned int streamid)
 {
-  unsigned int streamid:6;
-  unsigned int fmt:2;
-  unsigned int timestamp:24;
-  unsigned int length:24;
-  unsigned char type;
-  unsigned int msgid;
-};
-#pragma pack(pop)
-
-extern unsigned int flip(unsigned int bits, int bytecount);
-
-void amfinit(struct amfmsg* msg)
-{
-  msg->len=0;
-  msg->buf=malloc(sizeof(struct rtmph));
-}
-
-void amfsend(struct amfmsg* msg, int sock)
-{
-  struct rtmph* head=(struct rtmph*)msg->buf;
-  head->streamid=3;
-  head->fmt=0;
-  head->timestamp=0; // Time is irrelevant
-  head->length=flip(msg->len, 3);
-  head->type=20;
-  head->msgid=0;
-  // Send 128 bytes at a time separated by 0xc3 (because apparently that's something RTMP requires)
-// printf("Writing %i-byte message\n", msg->len);
-  write(sock, msg->buf, sizeof(struct rtmph));
-  unsigned char* pos=msg->buf+sizeof(struct rtmph);
-  while(msg->len>0)
-  {
-    int w;
-    if(msg->len>128)
-    {
-      w=write(sock, pos, 128);
-      w+=write(sock, "\xc3", 1);
-      msg->len-=128;
-    }else{
-      w=write(sock, pos, msg->len);
-      msg->len=0;
-    }
-// printf("Wrote %i bytes\n", w);
-    pos+=128;
-  }
-  free(msg->buf);
+  msg->type=RTMP_AMF0;
+  msg->streamid=streamid;
+  msg->length=0;
   msg->buf=0;
 }
 
-void amfnum(struct amfmsg* msg, double v)
+void amfnum(struct rtmp* msg, double v)
 {
-  int offset=sizeof(struct rtmph)+msg->len;
-  msg->len+=1+sizeof(double);
-  msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
+  int offset=msg->length;
+  msg->length+=1+sizeof(double);
+  msg->buf=realloc(msg->buf, msg->length);
   unsigned char* type=msg->buf+offset;
   type[0]='\x00';
   memcpy(msg->buf+offset+1, &v, sizeof(v));
 }
 
-void amfbool(struct amfmsg* msg, char v)
+void amfbool(struct rtmp* msg, char v)
 {
-  int offset=sizeof(struct rtmph)+msg->len;
-  msg->len+=2;
-  msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
+  int offset=msg->length;
+  msg->length+=2;
+  msg->buf=realloc(msg->buf, msg->length);
   unsigned char* x=msg->buf+offset;
   x[0]='\x01';
   x[1]=!!v;
 }
 
-void amfstring(struct amfmsg* msg, const char* string)
+void amfstring(struct rtmp* msg, const char* string)
 {
   int len=strlen(string);
-  int offset=sizeof(struct rtmph)+msg->len;
-  msg->len+=3+len;
-  msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
+  int offset=msg->length;
+  msg->length+=3+len;
+  msg->buf=realloc(msg->buf, msg->length);
   unsigned char* type=msg->buf+offset;
   type[0]='\x02';
   uint16_t* strlength=(uint16_t*)(msg->buf+offset+1);
@@ -107,41 +63,41 @@ void amfstring(struct amfmsg* msg, const char* string)
   memcpy(msg->buf+offset+3, string, len);
 }
 
-void amfobjstart(struct amfmsg* msg)
+void amfobjstart(struct rtmp* msg)
 {
-  int offset=sizeof(struct rtmph)+msg->len;
-  msg->len+=1;
-  msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
+  int offset=msg->length;
+  msg->length+=1;
+  msg->buf=realloc(msg->buf, msg->length);
   unsigned char* type=msg->buf+offset;
   type[0]='\x03';
 }
 
-void amfobjitem(struct amfmsg* msg, char* name)
+void amfobjitem(struct rtmp* msg, char* name)
 {
   int len=strlen(name);
-  int offset=sizeof(struct rtmph)+msg->len;
-  msg->len+=2+len;
-  msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
+  int offset=msg->length;
+  msg->length+=2+len;
+  msg->buf=realloc(msg->buf, msg->length);
   uint16_t* strlength=(uint16_t*)(msg->buf+offset);
   *strlength=htobe16(len);
   memcpy(msg->buf+offset+2, name, len);
 }
 
-void amfobjend(struct amfmsg* msg)
+void amfobjend(struct rtmp* msg)
 {
   amfobjitem(msg, "");
-  int offset=sizeof(struct rtmph)+msg->len;
-  msg->len+=1;
-  msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
+  int offset=msg->length;
+  msg->length+=1;
+  msg->buf=realloc(msg->buf, msg->length);
   unsigned char* type=msg->buf+offset;
   type[0]='\x09';
 }
 
-void amfnull(struct amfmsg* msg)
+void amfnull(struct rtmp* msg)
 {
-  int offset=sizeof(struct rtmph)+msg->len;
-  msg->len+=1;
-  msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
+  int offset=msg->length;
+  msg->length+=1;
+  msg->buf=realloc(msg->buf, msg->length);
   unsigned char* type=msg->buf+offset;
   type[0]='\x05';
 }
diff --git a/amfwriter.h b/amfwriter.h
index 3828ad0..62f735e 100644
--- a/amfwriter.h
+++ b/amfwriter.h
@@ -1,6 +1,6 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
-    Copyright (C) 2014  alicia@ion.nu
+    Copyright (C) 2014-2015  alicia@ion.nu
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -15,18 +15,13 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-struct amfmsg
-{
-  unsigned int len;
-  unsigned char* buf;
-};
+extern void amfinit(struct rtmp* msg, unsigned int streamid);
+extern void amfnum(struct rtmp* msg, double v);
+extern void amfbool(struct rtmp* msg, char v);
+extern void amfstring(struct rtmp* msg, const char* string);
+extern void amfobjstart(struct rtmp* msg);
+extern void amfobjitem(struct rtmp* msg, char* name);
+extern void amfobjend(struct rtmp* msg);
+extern void amfnull(struct rtmp* msg);
 
-extern void amfinit(struct amfmsg* msg);
-extern void amfsend(struct amfmsg* msg, int sock);
-extern void amfnum(struct amfmsg* msg, double v);
-extern void amfbool(struct amfmsg* msg, char v);
-extern void amfstring(struct amfmsg* msg, const char* string);
-extern void amfobjstart(struct amfmsg* msg);
-extern void amfobjitem(struct amfmsg* msg, char* name);
-extern void amfobjend(struct amfmsg* msg);
-extern void amfnull(struct amfmsg* msg);
+#define amfsend(rtmp,sock) rtmp_send(sock,rtmp); free((rtmp)->buf); (rtmp)->buf=0
diff --git a/client.c b/client.c
index 811965a..c0fb10c 100644
--- a/client.c
+++ b/client.c
@@ -1,7 +1,7 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
-    Copyright (C) 2014  alicia@ion.nu
-    Copyright (C) 2014  Jade Lea
+    Copyright (C) 2014-2015  alicia@ion.nu
+    Copyright (C) 2014-2015  Jade Lea
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -198,10 +198,10 @@ int main(int argc, char** argv)
   write(sock, handshake, 1536); // Send server's junk back
   b_read(sock, handshake, 1536); // Read our junk back, we don't bother checking that it's the same
   printf("Handshake complete\n");
-  // Handshake complete, send connect request
   struct rtmp rtmp={0,0,0};
-  struct amfmsg amf;
-  amfinit(&amf);
+  // Handshake complete, send connect request
+  struct rtmp amf;
+  amfinit(&amf, 3);
   amfstring(&amf, "connect");
   amfnum(&amf, 0);
   amfobjstart(&amf);
@@ -244,21 +244,11 @@ int main(int argc, char** argv)
   amfobjend(&amf);
   amfstring(&amf, channel);
   amfstring(&amf, "none");
-  amfstring(&amf, "show"); // This item is called roomtype in the same HTTP response that gives us the server (IP+port) to connect to
+  amfstring(&amf, "default"); // This item is called roomtype in the same HTTP response that gives us the server (IP+port) to connect to, but "default" seems to work fine too.
   amfstring(&amf, "tinychat");
   amfstring(&amf, "");
   amfsend(&amf, sock);
 
-  unsigned char buf[2048];
-  int len=read(sock, buf, 2048);
-//  printf("Received %i byte response\n", len);
-/* Debugging
-  int f=open("output", O_WRONLY|O_CREAT|O_TRUNC, 0644);
-  write(f, buf, len);
-  close(f);
-*/
-
-//  int outnum=2; (Debugging, number for output filenames)
   struct pollfd pfd[2];
   pfd[0].fd=0;
   pfd[0].events=POLLIN;
@@ -273,7 +263,8 @@ int main(int argc, char** argv)
     if(pfd[0].revents) // Got input, send a privmsg command
     {
       pfd[0].revents=0;
-      len=read(0, buf, 2047);
+      unsigned char buf[2048];
+      unsigned int len=read(0, buf, 2047);
       if(len<1){break;}
       while(len>0 && (buf[len-1]=='\n'||buf[len-1]=='\r')){--len;}
       if(!len){continue;} // Don't send empty lines
@@ -308,13 +299,11 @@ int main(int argc, char** argv)
         }
         else if(!strncmp((char*)buf, "/nick ", 6))
         {
-          free(nickname);
-          nickname=strdup((char*)&buf[6]);
-          amfinit(&amf);
+          amfinit(&amf, 3);
           amfstring(&amf, "nick");
           amfnum(&amf, 0);
           amfnull(&amf);
-          amfstring(&amf, nickname);
+          amfstring(&amf, (char*)&buf[6]);
           amfsend(&amf, sock);
           continue;
         }
@@ -334,7 +323,7 @@ int main(int argc, char** argv)
       wchar_t wcsbuf[len+1];
       mbstowcs(wcsbuf, (char*)buf, len+1);
       char* msg=tonumlist(wcsbuf);
-      amfinit(&amf);
+      amfinit(&amf, 3);
       amfstring(&amf, "privmsg");
       amfnum(&amf, 0);
       amfnull(&amf);
@@ -371,7 +360,7 @@ int main(int argc, char** argv)
         printf("Guest ID: %s\n", id);
         char* key=getkey(id, channel);
 
-        amfinit(&amf);
+        amfinit(&amf, 3);
         amfstring(&amf, "cauth");
         amfnum(&amf, 0);
         amfnull(&amf); // Means nothing but is apparently critically important for cauth at least
@@ -379,7 +368,7 @@ int main(int argc, char** argv)
         amfsend(&amf, sock);
         free(key);
 
-        amfinit(&amf);
+        amfinit(&amf, 3);
         amfstring(&amf, "nick");
         amfnum(&amf, 0);
         amfnull(&amf);
@@ -433,6 +422,11 @@ int main(int argc, char** argv)
       // nick
       else if(amfin->itemcount==5 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "nick") && amfin->items[2].type==AMF_STRING && amfin->items[3].type==AMF_STRING)
       {
+        if(!strcmp(amfin->items[2].string.string, nickname)) // Successfully changed our own nickname
+        {
+          free(nickname);
+          nickname=strdup(amfin->items[3].string.string);
+        }
         idlist_rename(amfin->items[2].string.string, amfin->items[3].string.string);
         printf("%s %s changed nickname to %s\n", timestamp(), amfin->items[2].string.string, amfin->items[3].string.string);
         fflush(stdout);
@@ -475,15 +469,27 @@ int main(int argc, char** argv)
       // oper, identifies mods
       else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "oper") && amfin->items[3].type==AMF_STRING)
       {
-        idlist_set_op(amfin->items[3].string.string);
+        idlist_set_op(amfin->items[3].string.string, 1);
         printf("%s is a moderator.\n", amfin->items[3].string.string);
         fflush(stdout);
       }
+      // deop, removes moderator privilege
+      else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "deop") && amfin->items[3].type==AMF_STRING)
+      {
+        idlist_set_op(amfin->items[3].string.string, 0);
+        printf("%s is no longer a moderator.\n", amfin->items[3].string.string);
+        fflush(stdout);
+      }
+      // nickinuse, the nick we wanted to change to is already taken
+      else if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING &&  amf_comparestrings_c(&amfin->items[0].string, "nickinuse"))
+      {
+        printf("Nick %s is already in use.\n", nickname);
+        fflush(stdout);
+      }
       // else{printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
       amf_free(amfin);
     }
     else{printf("Server disconnected\n"); break;}
-//    ++outnum; (Debugging)
   }
   free(rtmp.buf);
   close(sock);
diff --git a/idlist.c b/idlist.c
index c7fe7a3..8cc0e16 100644
--- a/idlist.c
+++ b/idlist.c
@@ -1,6 +1,7 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
     Copyright (C) 2014  alicia@ion.nu
+    Copyright (C) 2014-2015  Jade Lea
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -74,14 +75,14 @@ int idlist_get(const char* name)
   return -1;
 }
 
-void idlist_set_op(const char* name)
+void idlist_set_op(const char* name, char op)
 {
   int i;
   for(i=0; i<idlistlen; ++i)
   {
     if(!strcmp(name, idlist[i].name))
     {
-      idlist[i].op=1;
+      idlist[i].op=op;
       return;
     }
   }
diff --git a/idlist.h b/idlist.h
index c08ab8d..b7c7f81 100644
--- a/idlist.h
+++ b/idlist.h
@@ -1,6 +1,7 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
     Copyright (C) 2014  alicia@ion.nu
+    Copyright (C) 2014-2015  Jade Lea
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -28,5 +29,5 @@ extern void idlist_add(int id, const char* name);
 extern void idlist_remove(const char* name);
 extern void idlist_rename(const char* oldname, const char* newname);
 extern int idlist_get(const char* name);
-extern void idlist_set_op(const char* name);
+extern void idlist_set_op(const char* name, char op);
 extern char idlist_is_op(const char* name);
diff --git a/irchack.c b/irchack.c
index c13fa8b..67a45dc 100644
--- a/irchack.c
+++ b/irchack.c
@@ -1,6 +1,7 @@
 /*
     irchack, a simple application to reuse IRC clients as user interfaces for tc_client
     Copyright (C) 2014  alicia@ion.nu
+    Copyright (C) 2015  Jade Lea
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -252,6 +253,12 @@ printf("Got from tc_client: '%s'\n", buf);
         dprintf(sock, ":irchack MODE #%s +o %s\n", channel, buf);
         continue;
       }
+      if(space && !strcmp(space, " is no longer a moderator."))
+      {
+        space[0]=0;
+        dprintf(sock, ":irchack MODE #%s -o %s\n", channel, buf);
+        continue;
+      }
       if(buf[0]!='['){continue;} // Beyond this we only care about timestamped lines
       // Translate ANSI escape codes to IRC color code instead
       char* ansi;
diff --git a/rtmp.c b/rtmp.c
index bbb4da8..7ae7022 100644
--- a/rtmp.c
+++ b/rtmp.c
@@ -1,6 +1,6 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
-    Copyright (C) 2014  alicia@ion.nu
+    Copyright (C) 2015  alicia@ion.nu
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -80,3 +80,48 @@ char rtmp_get(int sock, struct rtmp* rtmp)
   }
   return 1;
 }
+
+void rtmp_send(int sock, struct rtmp* rtmp)
+{
+  // Header format and stream ID
+  unsigned char basicheader=(rtmp->streamid<64?rtmp->streamid:(rtmp->streamid<256?0:1)) | (1<<6);
+  write(sock, &basicheader, sizeof(basicheader));
+  if(rtmp->streamid>=64) // Handle large stream IDs
+  {
+    if(rtmp->streamid<256)
+    {
+      unsigned char streamid=rtmp->streamid-64;
+      write(sock, &streamid, sizeof(streamid));
+    }else{
+      unsigned short streamid=htole16(rtmp->streamid-64);
+      write(sock, &streamid, sizeof(streamid));
+    }
+  }
+  unsigned int x=0;
+  // Timestamp
+  write(sock, &x, 3); // Time is irrelevant
+  // Length
+  x=htobe32(rtmp->length);
+  write(sock, ((void*)&x)+1, 3);
+  // Type
+  write(sock, &rtmp->type, sizeof(rtmp->type));
+  // TODO: is there a situation where message IDs are needed? (format 0 header)
+  // Send 128 bytes at a time separated by 0xc3 (because apparently that's something RTMP requires)
+  void* pos=rtmp->buf;
+  unsigned int len=rtmp->length;
+  while(len>0)
+  {
+    int w;
+    if(len>128)
+    {
+      w=write(sock, pos, 128);
+      w+=write(sock, "\xc3", 1);
+      len-=128;
+    }else{
+      w=write(sock, pos, len);
+      len=0;
+    }
+// printf("Wrote %i bytes\n", w);
+    pos+=128;
+  }
+}
diff --git a/rtmp.h b/rtmp.h
index b426079..3326286 100644
--- a/rtmp.h
+++ b/rtmp.h
@@ -1,6 +1,6 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
-    Copyright (C) 2014  alicia@ion.nu
+    Copyright (C) 2015  alicia@ion.nu
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -33,3 +33,4 @@ struct rtmp
 };
 
 extern char rtmp_get(int sock, struct rtmp* rtmp);
+extern void rtmp_send(int sock, struct rtmp* rtmp);