Improve the program a bit, and include a protocol documentation

This commit is contained in:
techmetx11 2024-04-29 19:14:08 +01:00
parent 48be34ff5c
commit a124a26d36
No known key found for this signature in database
GPG Key ID: 20E0C88A0E7E5AF2
4 changed files with 101 additions and 10 deletions

46
README.md Normal file
View File

@ -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 |

View File

@ -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";

View File

@ -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;

View File

@ -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 => {