1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::path::PathBuf;
use std::str::FromStr;
use anyhow::{bail, format_err, Context};
use memofs::Vfs;
use reqwest::{
header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT},
StatusCode,
};
use structopt::StructOpt;
use crate::{auth_cookie::get_auth_cookie, serve_session::ServeSession};
use super::resolve_path;
#[derive(Debug, StructOpt)]
pub struct UploadCommand {
#[structopt(default_value = "")]
pub project: PathBuf,
#[structopt(long)]
pub cookie: Option<String>,
#[structopt(long = "asset_id")]
pub asset_id: u64,
}
impl UploadCommand {
pub fn run(self) -> Result<(), anyhow::Error> {
let project_path = resolve_path(&self.project);
let cookie = self.cookie.or_else(get_auth_cookie).context(
"Rojo could not find your Roblox auth cookie. Please pass one via --cookie.",
)?;
let vfs = Vfs::new_default();
let session = ServeSession::new(vfs, project_path)?;
let tree = session.tree();
let inner_tree = tree.inner();
let root = inner_tree.root();
let encode_ids = match root.class.as_str() {
"DataModel" => root.children().to_vec(),
_ => vec![root.referent()],
};
let mut buffer = Vec::new();
log::trace!("Encoding binary model");
rbx_binary::to_writer(&mut buffer, tree.inner(), &encode_ids)?;
do_upload(buffer, self.asset_id, &cookie)
}
}
#[derive(Debug, Clone, Copy)]
enum UploadKind {
Place,
Model,
}
impl FromStr for UploadKind {
type Err = anyhow::Error;
fn from_str(source: &str) -> Result<Self, Self::Err> {
match source {
"place" => Ok(UploadKind::Place),
"model" => Ok(UploadKind::Model),
attempted => Err(format_err!(
"Invalid upload kind '{}'. Valid kinds are: place, model",
attempted
)),
}
}
}
fn do_upload(buffer: Vec<u8>, asset_id: u64, cookie: &str) -> anyhow::Result<()> {
let url = format!(
"https://data.roblox.com/Data/Upload.ashx?assetid={}",
asset_id
);
let client = reqwest::Client::new();
let build_request = move || {
client
.post(&url)
.header(COOKIE, format!(".ROBLOSECURITY={}", cookie))
.header(USER_AGENT, "Roblox/WinInet")
.header(CONTENT_TYPE, "application/xml")
.header(ACCEPT, "application/json")
.body(buffer.clone())
};
log::debug!("Uploading to Roblox...");
let mut response = build_request().send()?;
if response.status() == StatusCode::FORBIDDEN {
if let Some(csrf_token) = response.headers().get("X-CSRF-Token") {
log::debug!("Received CSRF challenge, retrying with token...");
response = build_request().header("X-CSRF-Token", csrf_token).send()?;
}
}
let status = response.status();
if !status.is_success() {
bail!(
"The Roblox API returned an unexpected error: {}",
response.text()?
);
}
Ok(())
}