diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b766005
--- /dev/null
+++ b/README.md
@@ -0,0 +1,46 @@
+# Protocol Format
+
+All data-types bigger than 1 byte are stored in network endian (big-endian) unless stated otherwise.
+
+## Request Base
+| Name      | Size (bytes) | Description                          |
+|-----------|--------------|--------------------------------------|
+|opcode     | 1            | The operation code to perform, A list of operations currently supported (and their data) can be found in the **Operations** chapter |
+|request_id | 4            | The ID for the current request, Used to distinguish responses in the current connection |
+
+The data afterwards depends on the supplied opcode, Please consult the **Operations** chapter for more information.
+
+## Response Base
+| Name       | Size (bytes) | Description                           |
+|------------|--------------|---------------------------------------|
+|request_id  | 4            | The ID for the request that this response is meant for |
+
+The data afterwards depends on the supplied opcode, Please consult the **Operations** chapter for more information.
+
+## Operations
+### `FORCE_UPDATE` (0x00)
+Forces the server to re-fetch the YouTube player, and extract the necessary components from it (`nsig` function code, `sig` function code, signature timestamp).
+
+#### Request
+*No additional data required*
+
+#### Response
+| Name | Size (bytes) | Description |
+|------|--------------|-------------|
+|status| 4            | The status code of the request: `0xF44F` if successful, `0xFFFF` if no updating is required (YouTube's player ID is equal to the server's current player ID), `0x0000` if an error occurred |
+
+### `DECRYPT_N_SIGNATURE` (0x01)
+Decrypt a provided `n` signature using the server's current `nsig` function code, and return the result (or an error).
+
+#### Request
+| Name | Size (bytes) | Description                         |
+|------|--------------|-------------------------------------|
+|size  | 2            | The size of the encrypted signature |
+|string| *`size`*     | The encrypted signature             |
+
+#### Response
+| Name | Size (bytes) | Description                                                      |
+|------|--------------|------------------------------------------------------------------|
+|size  | 2            | The size of the decrypted signature, `0x0000` if an error occurred |
+|string| *`size`*     | The decrypted signature                                          |
+
diff --git a/src/consts.rs b/src/consts.rs
index 42febad..69b8f10 100644
--- a/src/consts.rs
+++ b/src/consts.rs
@@ -8,5 +8,11 @@ pub static REGEX_PLAYER_ID: &Lazy<Regex> = regex!("\\/s\\/player\\/([0-9a-f]{8})
 pub static NSIG_FUNCTION_ARRAY: &Lazy<Regex> = regex!(
     "\\.get\\(\"n\"\\)\\)&&\\([a-zA-Z0-9$_]=([a-zA-Z0-9$_]+)(?:\\[(\\d+)])?\\([a-zA-Z0-9$_]\\)"
 );
+pub static REGEX_SIGNATURE_TIMESTAMP: &Lazy<Regex> = regex!("signatureTimestamp[=:](\\d+)");
+
+pub static REGEX_SIGNATURE_FUNCTION: &Lazy<Regex> =
+    regex!("\\bc&&\\(c=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(c\\)\\)");
+pub static REGEX_HELPER_OBJ_NAME: &Lazy<Regex> = regex!(";([A-Za-z0-9_\\$]{2,})\\...\\(");
 
 pub static NSIG_FUNCTION_NAME: &str = "decrypt_nsig";
+pub static SIG_FUNCTION_NAME: &str = "decrypt_sig";
diff --git a/src/jobs.rs b/src/jobs.rs
index 9380889..1fb2474 100644
--- a/src/jobs.rs
+++ b/src/jobs.rs
@@ -15,6 +15,8 @@ use crate::consts::{NSIG_FUNCTION_ARRAY, NSIG_FUNCTION_NAME, REGEX_PLAYER_ID, TE
 pub enum JobOpcode {
     ForceUpdate,
     DecryptNSignature,
+    DecryptSignature,
+    GetSignatureTimestamp,
     UnknownOpcode,
 }
 
@@ -23,6 +25,8 @@ impl std::fmt::Display for JobOpcode {
         match self {
             Self::ForceUpdate => write!(f, "ForceUpdate"),
             Self::DecryptNSignature => write!(f, "DecryptNSignature"),
+            Self::DecryptSignature => write!(f, "DecryptSignature"),
+            Self::GetSignatureTimestamp => write!(f, "GetSignatureTimestamp"),
             Self::UnknownOpcode => write!(f, "UnknownOpcode"),
         }
     }
@@ -32,9 +36,8 @@ impl From<u8> for JobOpcode {
         match value {
             0x00 => Self::ForceUpdate,
             0x01 => Self::DecryptNSignature,
-
-            // make debugging easier
-            b'a' => Self::ForceUpdate,
+            0x02 => Self::DecryptSignature,
+            0x03 => Self::GetSignatureTimestamp,
             _ => Self::UnknownOpcode,
         }
     }
@@ -94,19 +97,40 @@ impl GlobalState {
         }
     }
 }
-pub async fn process_fetch_update(state: Arc<GlobalState>) {
+
+macro_rules! write_failure {
+    ($s:ident, $r:ident) => {
+        $s.write_u32($r).await;
+        $s.write_u16(0x0000).await;
+    };
+}
+
+pub async fn process_fetch_update<W>(
+    state: Arc<GlobalState>,
+    stream: Arc<Mutex<W>>,
+    request_id: u32,
+) where
+    W: tokio::io::AsyncWrite + Unpin + Send,
+{
+    let cloned_writer = stream.clone();
+    let mut writer = cloned_writer.lock().await;
+
     let global_state = state.clone();
     let response = match reqwest::get(TEST_YOUTUBE_VIDEO).await {
         Ok(req) => req.text().await.unwrap(),
         Err(x) => {
             println!("Could not fetch the test video: {}", x);
+            write_failure!(writer, request_id);
             return;
         }
     };
 
     let player_id_str = match REGEX_PLAYER_ID.captures(&response).unwrap().get(1) {
         Some(result) => result.as_str(),
-        None => return,
+        None => {
+            write_failure!(writer, request_id);
+            return;
+        }
     };
 
     let player_id: u32 = u32::from_str_radix(player_id_str, 16).unwrap();
@@ -118,6 +142,8 @@ pub async fn process_fetch_update(state: Arc<GlobalState>) {
 
     if player_id == current_player_id {
         // Player is already up to date
+        writer.write_u32(request_id).await;
+        writer.write_u16(0xFFFF).await;
         return;
     }
 
@@ -130,6 +156,7 @@ pub async fn process_fetch_update(state: Arc<GlobalState>) {
         Ok(req) => req.text().await.unwrap(),
         Err(x) => {
             println!("Could not fetch the player JS: {}", x);
+            write_failure!(writer, request_id);
             return;
         }
     };
@@ -152,6 +179,7 @@ pub async fn process_fetch_update(state: Arc<GlobalState>) {
         Ok(x) => x,
         Err(x) => {
             println!("Error: nsig regex compilation failed: {}", x);
+            write_failure!(writer, request_id);
             return;
         }
     };
@@ -186,10 +214,19 @@ pub async fn process_fetch_update(state: Arc<GlobalState>) {
         .unwrap()
         .as_str();
 
+    // Extract signature function name
+
     current_player_info = global_state.player_info.lock().await;
     current_player_info.player_id = player_id;
     current_player_info.nsig_function_code = nsig_function_code;
-    println!("Successfully updated the player")
+
+    writer.write_u32(request_id).await;
+    // sync code to tell the client the player had updated
+    writer.write_u16(0xF44F).await;
+
+    writer.flush().await;
+
+    println!("Successfully updated the player");
 }
 
 pub async fn process_decrypt_n_signature<W>(
@@ -200,6 +237,8 @@ pub async fn process_decrypt_n_signature<W>(
 ) where
     W: tokio::io::AsyncWrite + Unpin + Send,
 {
+    let cloned_writer = stream.clone();
+    let mut writer = cloned_writer.lock().await;
     let global_state = state.clone();
 
     println!("Signature to be decrypted: {}", sig);
@@ -219,6 +258,7 @@ pub async fn process_decrypt_n_signature<W>(
                     } else {
                         println!("JavaScript interpreter error (nsig code): {}", n);
                     }
+                    write_failure!(writer, request_id);
                     return;
                 }
             }
@@ -240,13 +280,11 @@ pub async fn process_decrypt_n_signature<W>(
                 } else {
                     println!("JavaScript interpreter error (nsig code): {}", n);
                 }
+                write_failure!(writer, request_id);
                 return;
             }
         };
 
-        let cloned_writer = stream.clone();
-        let mut writer = cloned_writer.lock().await;
-
         writer.write_u32(request_id).await;
         writer.write_u16(u16::try_from(decrypted_string.len()).unwrap()).await;
         writer.write_all(decrypted_string.as_bytes()).await;
diff --git a/src/main.rs b/src/main.rs
index 8ddc5b9..f519e40 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -86,8 +86,9 @@ async fn process_socket(state: Arc<GlobalState>, socket: UnixStream) -> Result<(
         match opcode {
             JobOpcode::ForceUpdate => {
                 let cloned_state = state.clone();
+                let cloned_stream = cloned_writestream.clone();
                 tokio::spawn(async move {
-                    process_fetch_update(cloned_state).await;
+                    process_fetch_update(cloned_state, cloned_stream, request_id).await;
                 });
             }
             JobOpcode::DecryptNSignature => {