Compare commits

...

14 Commits

Author SHA1 Message Date
Lucien Greathouse
eddc469f95 Release 6.2.0 2021-06-10 00:45:10 -04:00
Lucien Greathouse
21a4667fe4 Fix failing snapshot 2021-06-10 00:44:53 -04:00
Lucien Greathouse
b25f2fcd5d Update dependencies 2021-06-10 00:41:35 -04:00
Lucien Greathouse
0f7c9493d2 Fix 'Open Scripts Externally' crashing studio.
Closes #369.
2021-04-23 17:08:11 -04:00
Lucien Greathouse
f1c4102d7f Update changelog 2021-04-23 16:00:45 -04:00
Lucien Greathouse
8b5bfd5f44 Mark two-way sync as experimental in UI 2021-04-23 15:59:31 -04:00
Lucien Greathouse
0599b50235 Release 6.1.0 2021-04-12 17:19:35 -04:00
Lucien Greathouse
21f7ef6186 Update dependencies 2021-04-09 18:44:03 -04:00
MSAA
de6470bb45 change server bind address (#403)
* web/mod.rs - change server bind address

127.0.0.1 is a loopback interface, and only works on the same host
0.0.0.0 will allow connections from other hosts

ideally, this should be a console arg - but it's a quick fix

* implement --address option, revert default bind address to 127.0.0.1

* revert silly autoformatting

* ok, actually using rustfmt now

* More precise --address flag description

* Use SocketAddr where available, take advantage of const-ness

* Display 'localhost' if address is loopback

* Update Changelog

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2021-03-31 16:44:10 -04:00
Lucien Greathouse
b84aab0960 Release v6.0.2 2021-02-09 11:45:58 -05:00
Lucien Greathouse
0e89b91c38 Implement CSRF challenge support in upload 2021-02-09 11:36:59 -05:00
Lucien Greathouse
7888a704e1 Release 6.0.1 2021-01-22 13:47:39 -07:00
Lucien Greathouse
804fd3de8e Remove Requester header to handle API change 2021-01-22 13:41:55 -07:00
Lucien Greathouse
4992c36f08 Delete ErrorDisplay, use anyhow instead! 2021-01-18 14:54:12 -07:00
15 changed files with 460 additions and 435 deletions

View File

@@ -2,6 +2,28 @@
## Unreleased Changes ## Unreleased Changes
## [6.2.0][6.2.0] (June 10, 2021)
* Added "EXPERIMENTAL!" label to two-way sync toggle in Rojo's Roblox Studio plugin.
* Fixed "Open Scripts Externally" feature crashing Studio ([#369][issue-369])
* Updated dependencies, fixing `HumanoidDescription` ID issues.
[issue-369]: https://github.com/rojo-rbx/rojo/issues/369
[6.2.0]: https://github.com/rojo-rbx/rojo/releases/tag/v6.2.0
## [6.1.0][6.1.0] (April 12, 2021)
* Updated dependencies, fixing OptionalCoordinateFrame-related issues.
* Added `--address` flag to `rojo serve` to allow for external connections. ([#403][pr-403])
[pr-403]: https://github.com/rojo-rbx/rojo/pull/403
[6.1.0]: https://github.com/rojo-rbx/rojo/releases/tag/v6.1.0
## [6.0.2](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.2) (February 9, 2021)
* Fixed `rojo upload` to handle CSRF challenges.
## [6.0.1](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.1) (January 22, 2021)
* Fixed `rojo upload` requests being rejected by Roblox
## [6.0.0](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.0) (January 16, 2021) ## [6.0.0](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.0) (January 16, 2021)
* Improved server error messages * Improved server error messages
* The server will now keep running in more error cases * The server will now keep running in more error cases

744
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "rojo" name = "rojo"
version = "6.0.0" version = "6.2.0"
authors = ["Lucien Greathouse <me@lpghatguy.com>"] authors = ["Lucien Greathouse <me@lpghatguy.com>"]
description = "Enables professional-grade development tools for Roblox developers" description = "Enables professional-grade development tools for Roblox developers"
license = "MPL-2.0" license = "MPL-2.0"

View File

@@ -205,7 +205,7 @@ function SettingsPage:render()
TwoWaySync = e(Setting, { TwoWaySync = e(Setting, {
id = "twoWaySync", id = "twoWaySync",
name = "Two-Way Sync", name = "Two-Way Sync",
description = "Editing files in Studio will sync them into the filesystem", description = "EXPERIMENTAL! Editing files in Studio will sync them into the filesystem",
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = 2, layoutOrder = 2,
}), }),

View File

@@ -5,7 +5,7 @@ local isDevBuild = script.Parent.Parent:FindFirstChild("ROJO_DEV_BUILD") ~= nil
return strict("Config", { return strict("Config", {
isDevBuild = isDevBuild, isDevBuild = isDevBuild,
codename = "Epiphany", codename = "Epiphany",
version = {6, 0, 0}, version = {6, 2, 0},
expectedServerVersionString = "6.0 or newer", expectedServerVersionString = "6.0 or newer",
protocolVersion = 3, protocolVersion = 3,
defaultHost = "localhost", defaultHost = "localhost",

View File

@@ -1,4 +1,5 @@
local StudioService = game:GetService("StudioService") local StudioService = game:GetService("StudioService")
local RunService = game:GetService("RunService")
local Log = require(script.Parent.Parent.Log) local Log = require(script.Parent.Parent.Log)
local Fmt = require(script.Parent.Parent.Fmt) local Fmt = require(script.Parent.Parent.Fmt)
@@ -150,10 +151,18 @@ function ServeSession:__onActiveScriptChanged(activeScript)
Log.debug("Trying to open script {} externally...", activeScript) Log.debug("Trying to open script {} externally...", activeScript)
-- Force-close the script inside Studio -- Force-close the script inside Studio... with a small delay in the middle
local existingParent = activeScript.Parent -- to prevent Studio from crashing.
activeScript.Parent = nil spawn(function()
activeScript.Parent = existingParent local existingParent = activeScript.Parent
activeScript.Parent = nil
for i = 1, 3 do
RunService.Heartbeat:Wait()
end
activeScript.Parent = existingParent
end)
-- Notify the Rojo server to open this script -- Notify the Rojo server to open this script
self.__apiContext:open(scriptId) self.__apiContext:open(scriptId)

View File

@@ -1,6 +1,7 @@
--- ---
source: tests/tests/build.rs source: tests/tests/build.rs
expression: contents expression: contents
--- ---
<roblox version="4"> <roblox version="4">
<Item class="Folder" referent="0"> <Item class="Folder" referent="0">
@@ -10,7 +11,7 @@ expression: contents
<Item class="IntValue" referent="1"> <Item class="IntValue" referent="1">
<Properties> <Properties>
<string name="Name">simple-model</string> <string name="Name">simple-model</string>
<int name="Value">5</int> <int64 name="Value">5</int64>
</Properties> </Properties>
<Item class="Folder" referent="2"> <Item class="Folder" referent="2">
<Properties> <Properties>

View File

@@ -9,7 +9,6 @@ use memofs::{IoResultExt, Vfs, VfsEvent};
use rbx_dom_weak::{RbxId, RbxValue}; use rbx_dom_weak::{RbxId, RbxValue};
use crate::{ use crate::{
error::ErrorDisplay,
message_queue::MessageQueue, message_queue::MessageQueue,
snapshot::{ snapshot::{
apply_patch_set, compute_patch_set, AppliedPatchSet, InstigatingSource, PatchSet, RojoTree, apply_patch_set, compute_patch_set, AppliedPatchSet, InstigatingSource, PatchSet, RojoTree,
@@ -313,7 +312,7 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: RbxId) -> Optio
apply_patch_set(tree, patch_set) apply_patch_set(tree, patch_set)
} }
Err(err) => { Err(err) => {
log::error!("Error processing filesystem change: {}", ErrorDisplay(err)); log::error!("Error processing filesystem change: {:?}", err);
return None; return None;
} }
}, },

View File

@@ -12,6 +12,7 @@ use std::{
env, env,
error::Error, error::Error,
fmt, fmt,
net::IpAddr,
path::{Path, PathBuf}, path::{Path, PathBuf},
str::FromStr, str::FromStr,
}; };
@@ -186,7 +187,11 @@ pub struct ServeCommand {
#[structopt(default_value = "")] #[structopt(default_value = "")]
pub project: PathBuf, pub project: PathBuf,
/// The port to listen on. Defaults to the project's preference, or 34872 if /// The IP address to listen on. Defaults to `127.0.0.1`.
#[structopt(long)]
pub address: Option<IpAddr>,
/// The port to listen on. Defaults to the project's preference, or `34872` if
/// it has none. /// it has none.
#[structopt(long)] #[structopt(long)]
pub port: Option<u16>, pub port: Option<u16>,

View File

@@ -1,5 +1,7 @@
use std::{ use std::{
io::{self, Write}, io::{self, Write},
net::IpAddr,
net::Ipv4Addr,
sync::Arc, sync::Arc,
}; };
@@ -13,6 +15,7 @@ use crate::{
web::LiveServer, web::LiveServer,
}; };
const DEFAULT_BIND_ADDRESS: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
const DEFAULT_PORT: u16 = 34872; const DEFAULT_PORT: u16 = 34872;
pub fn serve(global: GlobalOptions, options: ServeCommand) -> Result<()> { pub fn serve(global: GlobalOptions, options: ServeCommand) -> Result<()> {
@@ -20,6 +23,8 @@ pub fn serve(global: GlobalOptions, options: ServeCommand) -> Result<()> {
let session = Arc::new(ServeSession::new(vfs, &options.absolute_project())?); let session = Arc::new(ServeSession::new(vfs, &options.absolute_project())?);
let ip = options.address.unwrap_or(DEFAULT_BIND_ADDRESS.into());
let port = options let port = options
.port .port
.or_else(|| session.project_port()) .or_else(|| session.project_port())
@@ -27,13 +32,13 @@ pub fn serve(global: GlobalOptions, options: ServeCommand) -> Result<()> {
let server = LiveServer::new(session); let server = LiveServer::new(session);
let _ = show_start_message(port, global.color.into()); let _ = show_start_message(ip, port, global.color.into());
server.start(port); server.start((ip, port).into());
Ok(()) Ok(())
} }
fn show_start_message(port: u16, color: ColorChoice) -> io::Result<()> { fn show_start_message(bind_address: IpAddr, port: u16, color: ColorChoice) -> io::Result<()> {
let writer = BufferWriter::stdout(color); let writer = BufferWriter::stdout(color);
let mut buffer = writer.buffer(); let mut buffer = writer.buffer();
@@ -41,7 +46,12 @@ fn show_start_message(port: u16, color: ColorChoice) -> io::Result<()> {
write!(&mut buffer, " Address: ")?; write!(&mut buffer, " Address: ")?;
buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?; buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
writeln!(&mut buffer, "localhost")?;
if bind_address.is_loopback() {
writeln!(&mut buffer, "localhost")?;
} else {
writeln!(&mut buffer, "{}", bind_address)?;
}
buffer.set_color(&ColorSpec::new())?; buffer.set_color(&ColorSpec::new())?;
write!(&mut buffer, " Port: ")?; write!(&mut buffer, " Port: ")?;

View File

@@ -1,5 +1,8 @@
use memofs::Vfs; use memofs::Vfs;
use reqwest::header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT}; use reqwest::{
header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT},
StatusCode,
};
use thiserror::Error; use thiserror::Error;
use crate::{auth_cookie::get_auth_cookie, cli::UploadCommand, serve_session::ServeSession}; use crate::{auth_cookie::get_auth_cookie, cli::UploadCommand, serve_session::ServeSession};
@@ -41,25 +44,42 @@ pub fn upload(options: UploadCommand) -> Result<(), anyhow::Error> {
.property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown); .property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown);
rbx_xml::to_writer(&mut buffer, tree.inner(), &encode_ids, config)?; rbx_xml::to_writer(&mut buffer, tree.inner(), &encode_ids, config)?;
do_upload(buffer, options.asset_id, &cookie)
}
fn do_upload(buffer: Vec<u8>, asset_id: u64, cookie: &str) -> anyhow::Result<()> {
let url = format!( let url = format!(
"https://data.roblox.com/Data/Upload.ashx?assetid={}", "https://data.roblox.com/Data/Upload.ashx?assetid={}",
options.asset_id asset_id
); );
log::trace!("POSTing to {}", url);
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let mut response = client
.post(&url)
.header(COOKIE, format!(".ROBLOSECURITY={}", &cookie))
.header(USER_AGENT, "Roblox/WinInet")
.header("Requester", "Client")
.header(CONTENT_TYPE, "application/xml")
.header(ACCEPT, "application/json")
.body(buffer)
.send()?;
if !response.status().is_success() { 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()?;
// Starting in Feburary, 2021, the upload endpoint performs CSRF challenges.
// If we receive an HTTP 403 with a X-CSRF-Token reply, we should retry the
// request, echoing the value of that header.
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() {
return Err(Error::RobloxApi { return Err(Error::RobloxApi {
body: response.text()?, body: response.text()?,
} }

View File

@@ -1,18 +0,0 @@
use std::{error::Error, fmt};
/// Wrapper type to print errors with source-chasing.
pub struct ErrorDisplay<E>(pub E);
impl<E: Error> fmt::Display for ErrorDisplay<E> {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
writeln!(formatter, "{}", self.0)?;
let mut current_err: &dyn Error = &self.0;
while let Some(source) = current_err.source() {
writeln!(formatter, " caused by {}", source)?;
current_err = &*source;
}
Ok(())
}
}

View File

@@ -9,7 +9,6 @@ mod tree_view;
mod auth_cookie; mod auth_cookie;
mod change_processor; mod change_processor;
mod error;
mod glob; mod glob;
mod lua_ast; mod lua_ast;
mod message_queue; mod message_queue;

View File

@@ -1,6 +1,7 @@
--- ---
source: src/snapshot_middleware/json_model.rs source: src/snapshot_middleware/json_model.rs
expression: instance_snapshot expression: instance_snapshot
--- ---
snapshot_id: ~ snapshot_id: ~
metadata: metadata:
@@ -14,7 +15,7 @@ name: foo
class_name: IntValue class_name: IntValue
properties: properties:
Value: Value:
Type: Int32 Type: Int64
Value: 5 Value: 5
children: children:
- snapshot_id: ~ - snapshot_id: ~
@@ -26,3 +27,4 @@ children:
class_name: StringValue class_name: StringValue
properties: {} properties: {}
children: [] children: []

View File

@@ -4,7 +4,7 @@ pub mod interface;
mod ui; mod ui;
mod util; mod util;
use std::sync::Arc; use std::{net::SocketAddr, sync::Arc};
use futures::{ use futures::{
future::{self, FutureResult}, future::{self, FutureResult},
@@ -57,9 +57,7 @@ impl LiveServer {
LiveServer { serve_session } LiveServer { serve_session }
} }
pub fn start(self, port: u16) { pub fn start(self, address: SocketAddr) {
let address = ([127, 0, 0, 1], port).into();
let server = Server::bind(&address) let server = Server::bind(&address)
.serve(move || { .serve(move || {
let service: FutureResult<_, hyper::Error> = let service: FutureResult<_, hyper::Error> =