commit 65692ec48109824ed9d14d16f47e625d5ce85c6e Author: Sebastian Hugentobler Date: Sun Mar 19 14:50:58 2017 +0100 initial proof of concept diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d741e40 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f0eba74 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*~ +.DS_Store +*.swp +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..fa640b9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,249 @@ +[root] +name = "gog-backup" +version = "0.1.0" +dependencies = [ + "curl 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "curl" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "curl-sys 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-probe 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "curl-sys" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libz-sys 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dtoa" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "gcc" +version = "0.3.43" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "gdi32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libz-sys" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "matches" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num-traits" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "openssl-probe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "openssl-sys" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)", + "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pkg-config" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_codegen_internals" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "syn 0.11.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_codegen_internals 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-bidi" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "url" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "user32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum curl 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c90e1240ef340dd4027ade439e5c7c2064dd9dc652682117bd50d1486a3add7b" +"checksum curl-sys 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d909dc402ae80b6f7b0118c039203436061b9d9a3ca5d2c2546d93e0a61aaa" +"checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" +"checksum gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)" = "c07c758b972368e703a562686adb39125707cc1ef3399da8c019fc6c2498a75d" +"checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" +"checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11" +"checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" +"checksum libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "88ee81885f9f04bff991e306fea7c1c60a5f0f9e409e99f6b40e3311a3363135" +"checksum libz-sys 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e5ee912a45d686d393d5ac87fac15ba0ba18daae14e8e7543c63ebf7fb7e970c" +"checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1" +"checksum num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "e1cbfa3781f3fe73dc05321bed52a06d2d491eaa764c52335cf4399f046ece99" +"checksum openssl-probe 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "756d49c8424483a3df3b5d735112b4da22109ced9a8294f1f5cdf80fb3810919" +"checksum openssl-sys 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ddd1154228cf62c05114a82e26b1a0093f90815367699ee3f3dafb62b9602111" +"checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)" = "a702319c807c016e51f672e5c77d6f0b46afddd744b5e437d6b8436b888b458f" +"checksum serde_codegen_internals 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d52006899f910528a10631e5b727973fe668f3228109d1707ccf5bad5490b6e" +"checksum serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f15ea24bd037b2d64646b4d934fa99c649be66e3f7b29fb595a5543b212b1452" +"checksum serde_json 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)" = "dbc45439552eb8fb86907a2c41c1fd0ef97458efb87ff7f878db466eb581824e" +"checksum syn 0.11.9 (registry+https://github.com/rust-lang/crates.io-index)" = "480c834701caba3548aa991e54677281be3a5414a9d09ddbdf4ed74a569a9d19" +"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum unicode-bidi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a078ebdd62c0e71a709c3d53d2af693fe09fe93fbff8344aebe289b78f9032" +"checksum unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e28fa37426fceeb5cf8f41ee273faa7c82c47dc8fba5853402841e665fcd86ff" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5ba8a749fb4479b043733416c244fa9d1d3af3d7c23804944651c8a448cb87e" +"checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fe8b11b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "gog-backup" +version = "0.1.0" +authors = ["Sebastian Hugentobler "] + +[dependencies] +url = "1.4.0" +serde = "0.9.11" +serde_derive = "0.9.11" +serde_json = "0.9" +curl = "0.4.6" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a0d3172 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,182 @@ +extern crate url; +extern crate curl; +extern crate serde; +extern crate serde_json; +#[macro_use] +extern crate serde_derive; + +use std::io; +use std::fs::File; +use std::io::Write; +use curl::easy::{Easy, List}; +use std::str; +use serde_json::Value; +use url::{Url, Host}; +use std::path::Path; + +#[derive(Serialize, Deserialize)] +struct Token { + access_token: String, + expires_in: u16, + token_type: String, + scope: String, + session_id: String, + refresh_token: String, + user_id: String, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct Game { + title: String, + cd_key: String, + #[serde(skip_deserializing)] + installers: Vec, + extras: Vec, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct Installer { + manual_url: String, + version: String, + os: String, + language: String, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct Extra { + manual_url: String, + name: String, +} + +fn main() { + const CLIENT_ID: &'static str = "46899977096215655"; + const CLIENT_SECRET: &'static str = "9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9"; + // const redirect_uri: &'static str = "https://embed.gog.com/on_login_success?origin=client"; + const REDIRECT_URI_QUOTED: &'static str = "https%3A%2F%2Fembed.gog.\ + com%2Fon_login_success%3Forigin%3Dclient"; + + let auth_url = format!("https://auth.gog.\ + com/auth?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&layout=client2", + client_id = CLIENT_ID, + redirect_uri = REDIRECT_URI_QUOTED); + + println!("{}", auth_url); + + let mut code = String::new(); + + print!("Code: "); + io::stdout().flush().unwrap(); + + io::stdin() + .read_line(&mut code) + .expect("Failed to read line"); + + let token_query = format!("https://auth.gog.\ + com/token?client_id={client_id}&client_secret={client_secret}&grant_type=authorization_code&code={code}&redirect_uri={redirect_uri_quoted}", + client_id = CLIENT_ID, + client_secret = CLIENT_SECRET, + code = code.trim(), + redirect_uri_quoted = REDIRECT_URI_QUOTED); + + let mut token_response_body = String::new(); + + let mut curl = Easy::new(); + curl.url(token_query.as_str()).unwrap(); + { + let mut transfer = curl.transfer(); + transfer.write_function(|data| { + token_response_body = String::from(str::from_utf8(data).unwrap()); + Ok(data.len()) + }) + .unwrap(); + transfer.perform().unwrap(); + } + + let token: Token = serde_json::from_str(&token_response_body).unwrap(); + + let mut list = List::new(); + list.append(format!("Authorization: Bearer {token}", token = token.access_token).as_str()) + .unwrap(); + curl.http_headers(list).unwrap(); + curl.follow_location(true); + + let mut games_response_body = String::new(); + curl.url("https://embed.gog.com/user/data/games").unwrap(); + { + let mut transfer = curl.transfer(); + transfer.write_function(|data| { + games_response_body = String::from(str::from_utf8(data).unwrap()); + Ok(data.len()) + }) + .unwrap(); + transfer.perform().unwrap(); + } + + let game_ids_raw: Value = serde_json::from_str(games_response_body.as_str()).unwrap(); + let game_ids = &game_ids_raw["owned"]; + + let mut game_response_body = String::new(); + let game_query = format!("https://embed.gog.com/account/gameDetails/{game_id}.json", + game_id = game_ids[10]); + curl.url(game_query.as_str()).unwrap(); + { + let mut transfer = curl.transfer(); + transfer.write_function(|data| { + game_response_body = String::from(str::from_utf8(data).unwrap()); + Ok(data.len()) + }) + .unwrap(); + transfer.perform().unwrap(); + } + + let mut game: Game = serde_json::from_str(&game_response_body).unwrap(); + + let game_raw: Value = serde_json::from_str(game_response_body.as_str()).unwrap(); + let downloads = &game_raw["downloads"]; + + for languages in downloads.as_array() { + for language in languages { + for systems in language[1].as_object() { + for system in systems.keys() { + for real_downloads in systems.get(system) { + for real_download in real_downloads.as_array() { + let download = &real_download[0]; + + let installer = Installer { + manual_url: String::from(download["manualUrl"].as_str().unwrap()), + version: String::from(download["manualUrl"].as_str().unwrap()), + os: system.clone(), + language: String::from(language[0].as_str().unwrap()), + }; + + game.installers.push(installer); + } + } + } + } + } + } + + let download_query = format!("https://embed.gog.com{}", game.installers[0].manual_url); + curl.url(download_query.as_str()).unwrap(); + curl.perform().unwrap(); + + let download_url = Url::parse(curl.effective_url().unwrap().unwrap()).unwrap(); + let file_name = download_url.path_segments().unwrap().last().unwrap(); + + let download_path = Path::new("/Users/sebastianhugentobler/Downloads/").join(file_name); + let mut file_download = File::create(download_path).unwrap(); + curl.url(download_query.as_str()).unwrap(); + { + let mut transfer = curl.transfer(); + transfer.write_function(|data| { + file_download.write(data); + Ok(data.len()) + }) + .unwrap(); + transfer.perform().unwrap(); + } +}