#![feature(array_chunks)] pub mod mesh; use anyhow::{Result, bail}; use glam::{DAffine3, DMat4, Vec3}; use log::{debug, error}; use mesh::convert_mesh; 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_all}, io::{AsyncReadExt, AsyncWriteExt}, }; use weareshared::{ Affine3A, resources::{Prefab, RespackEntry}, respack::save_full_respack, store::ResourceStore, }; #[tokio::main] async fn main() -> Result<()> { env_logger::init_from_env("LOG"); let c = GeClient::new().await?; let entry = c.planetoid_metdata().await?; let bulk = c .bulk_metadata("", entry.root_node_metadata.unwrap().bulk_metadata_epoch()) .await?; let store = ResourceStore::new_memory(); let mut prefabs = Vec::new(); for node_meta in &bulk.node_metadata { if unpack_path_and_id(node_meta.path_and_flags()).0.len() != 4 { // toplevel octs are not found // also i want most subdivided continue; } let Ok(node_data) = c.node_data(&bulk, &node_meta).await else { continue; }; let transform = DMat4::from_cols_slice(&node_data.matrix_globe_from_mesh); let mut meshes = Vec::new(); for m in node_data.meshes { let mesh = convert_mesh(m, &store)?; meshes.push(( Affine3A::from_mat4((transform / 3_000_000.).as_mat4()), mesh, )) } prefabs.push(store.set(&Prefab { transform: Some(DAffine3::from_mat4(transform)), mesh: meshes, ..Default::default() })?); } let entry = store.set(&RespackEntry { c_prefab: prefabs, ..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 = node .epoch .unwrap_or(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")); }