#![feature(array_chunks)] use anyhow::{Result, bail}; use glam::Vec3; use log::{debug, error}; use prost::{Message, bytes::Bytes}; use proto::{BulkMetadata, NodeData, NodeMetadata, PlanetoidMetadata}; use reqwest::{ Client, header::{HeaderMap, HeaderName, HeaderValue}, }; use std::path::PathBuf; use tokio::{ fs::{File, create_dir, create_dir_all}, io::{AsyncReadExt, AsyncWriteExt}, }; use weareshared::{ Affine3A, Vec3A, resources::{MeshPart, Prefab, RespackEntry}, respack::save_full_respack, store::ResourceStore, vec3a, }; #[tokio::main] async fn main() -> Result<()> { env_logger::init_from_env("LOG"); let c = GeClient::new().await?; let entry = c.planetoid_metdata().await?; eprintln!("{:#?}", entry); let mut bulk = c .bulk_metadata("", entry.root_node_metadata.unwrap().bulk_metadata_epoch()) .await?; eprintln!("{:#?}", bulk); let store = ResourceStore::new_memory(); let mut meshes = Vec::new(); for node_meta in &bulk.node_metadata { let Ok(mut node_data) = c.node_data(&bulk, &node_meta).await else { continue; }; for m in node_data.meshes { let mut index_strip = Vec::new(); let strip_len = m.indices[0]; let mut zeros = 0; for i in 0..strip_len { let val = m.indices[i as usize + 1]; index_strip.push((zeros - val) as u32); if val == 0 { zeros += 1; } } let mut index = Vec::new(); for i in 0..index_strip.len() - 2 { if i & 1 == 0 { index.push([index_strip[i + 0], index_strip[i + 1], index_strip[i + 2]]); } else { index.push([index_strip[i + 0], index_strip[i + 2], index_strip[i + 1]]); } } let mut positions = Vec::new(); let vert = m.vertices(); let vertex_count = vert.len() / 3; let (mut x, mut y, mut z) = (0u8, 0u8, 0u8); for i in 0..vertex_count { x = x.wrapping_add(vert[vertex_count * 0 + i]); y = y.wrapping_add(vert[vertex_count * 1 + i]); z = z.wrapping_add(vert[vertex_count * 2 + i]); positions.push(vec3a(x as f32, y as f32, z as f32)); } // eprintln!("{index:?}"); // eprintln!("{positions:?}"); meshes.push(( Affine3A::from_scale(Vec3::splat(0.01)), store.set(&MeshPart { index: Some(store.set(&index)?), va_position: Some(store.set(&positions)?), g_double_sided: Some(()), ..Default::default() })?, )) } } eprintln!("{}", meshes.len()); let prefab = store.set(&Prefab { mesh: meshes, ..Default::default() })?; let entry = store.set(&RespackEntry { c_prefab: vec![prefab], ..Default::default() })?; let file = std::fs::File::create("/tmp/a.respack")?; save_full_respack(file, &store, Some(entry))?; Ok(()) } struct GeClient { client: Client, cachedir: PathBuf, } impl GeClient { pub async fn new() -> Result { let cachedir = xdg::BaseDirectories::with_prefix("weareearth") .unwrap() .create_cache_directory("download") .unwrap(); create_dir_all(cachedir.join("BulkMetadata")).await?; create_dir_all(cachedir.join("NodeData")).await?; Ok(Self { cachedir, client: Client::builder().default_headers(HeaderMap::from_iter([ (HeaderName::from_static("user-agent"), HeaderValue::from_static("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36")), (HeaderName::from_static("referer"), HeaderValue::from_static("https://earth.google.com/")) ])).build().unwrap() }) } pub async fn download(&self, path: &str) -> Result { let cachepath = self.cachedir.join(path); if cachepath.exists() { debug!("cached {path:?}"); let mut buf = Vec::new(); File::open(cachepath).await?.read_to_end(&mut buf).await?; Ok(buf.into()) } else { debug!("download {path:?}"); let res = self .client .get(format!("https://kh.google.com/rt/earth/{path}")) .send() .await?; if !res.status().is_success() { error!("error response: {:?}", res.text().await?); bail!("error response") } let buf = res.bytes().await?; File::create(cachepath).await?.write_all(&buf).await?; Ok(buf) } } pub async fn planetoid_metdata(&self) -> Result { let buf = self.download("PlanetoidMetadata").await?; Ok(PlanetoidMetadata::decode(buf)?) } pub async fn bulk_metadata(&self, path: &str, epoch: u32) -> Result { let buf = self .download(&format!("BulkMetadata/pb=!1m2!1s{path}!2u{epoch}")) .await?; Ok(BulkMetadata::decode(buf)?) } pub async fn node_data(&self, bulk: &BulkMetadata, node: &NodeMetadata) -> Result { let (path, flags) = unpack_path_and_id(node.path_and_flags()); let texture_format = bulk.default_available_texture_formats(); let imagery_epoch = node.imagery_epoch.unwrap_or(bulk.default_imagery_epoch()); let node_epoch = bulk.head_node_key.as_ref().unwrap().epoch.unwrap(); // ? let image_epoch_part = if flags & 16 != 0 { format!("!3u{imagery_epoch}") } else { String::new() }; let url = format!( "NodeData/pb=!1m2!1s{path}!2u{node_epoch}!2e{texture_format}{image_epoch_part}!4b0" ); let buf = self.download(&url).await?; Ok(NodeData::decode(buf)?) } } fn unpack_path_and_id(mut path_id: u32) -> (String, u32) { let level = 1 + (path_id & 3); path_id >>= 2; let mut path = String::new(); for _ in 0..level { path += &(path_id & 7).to_string(); path_id >>= 3; } let flags = path_id; (path, flags) } pub mod proto { include!(concat!(env!("OUT_DIR"), "/earth.proto.rs")); }