mirror of
https://github.com/mat-1/azalea.git
synced 2024-09-19 22:52:32 +00:00
add stuff related to chat signing
and also some stuff related to digging because i forgot to do a different branch lol
This commit is contained in:
parent
9bdace4aab
commit
6188230009
13 changed files with 500 additions and 28 deletions
160
Cargo.lock
generated
160
Cargo.lock
generated
|
@ -198,11 +198,14 @@ version = "0.7.0"
|
|||
dependencies = [
|
||||
"azalea-buf",
|
||||
"azalea-crypto",
|
||||
"base64",
|
||||
"chrono",
|
||||
"env_logger",
|
||||
"log",
|
||||
"num-bigint",
|
||||
"parking_lot",
|
||||
"reqwest",
|
||||
"rsa",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
|
@ -282,6 +285,7 @@ dependencies = [
|
|||
"azalea-core",
|
||||
"azalea-crypto",
|
||||
"azalea-inventory",
|
||||
"azalea-nbt",
|
||||
"azalea-physics",
|
||||
"azalea-protocol",
|
||||
"azalea-registry",
|
||||
|
@ -328,8 +332,10 @@ dependencies = [
|
|||
"criterion",
|
||||
"num-bigint",
|
||||
"rand",
|
||||
"rsa",
|
||||
"rsa_public_encrypt_pkcs1",
|
||||
"sha-1",
|
||||
"sha2",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
@ -502,9 +508,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.0"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bevy_app"
|
||||
|
@ -802,6 +814,7 @@ checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
|
|||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -895,6 +908,12 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
|
@ -1014,6 +1033,17 @@ version = "2.3.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56acb310e15652100da43d130af8d97b509e95af61aab1c5a7939ef24337ee17"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.17"
|
||||
|
@ -1034,6 +1064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
|
@ -1541,6 +1572,9 @@ name = "lazy_static"
|
|||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
|
@ -1554,6 +1588,12 @@ version = "0.1.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
|
@ -1695,6 +1735,23 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2399c9463abc5f909349d8aa9ba080e0b88b3ce2885389b60b993f39b1a56905"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"lazy_static",
|
||||
"libm 0.2.7",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"smallvec",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.4.3"
|
||||
|
@ -1744,6 +1801,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm 0.2.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1796,7 +1854,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a1914cd452d8fccd6f9db48147b29fd4ae05bea9dc5d9ad578509f72415de282"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libm",
|
||||
"libm 0.1.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1831,6 +1889,15 @@ dependencies = [
|
|||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
|
@ -1859,6 +1926,27 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkcs1"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
|
||||
dependencies = [
|
||||
"der",
|
||||
"pkcs8",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.4"
|
||||
|
@ -2068,6 +2156,29 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"const-oid",
|
||||
"digest",
|
||||
"num-bigint-dig",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core",
|
||||
"sha2",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa_public_encrypt_pkcs1"
|
||||
version = "0.4.0"
|
||||
|
@ -2237,6 +2348,17 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.4"
|
||||
|
@ -2255,6 +2377,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple_asn1"
|
||||
version = "0.5.4"
|
||||
|
@ -2301,12 +2433,28 @@ version = "0.5.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
|
@ -3014,3 +3162,9 @@ dependencies = [
|
|||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
|
||||
|
|
|
@ -11,13 +11,16 @@ version = "0.7.0"
|
|||
[dependencies]
|
||||
azalea-buf = { path = "../azalea-buf", version = "^0.7.0" }
|
||||
azalea-crypto = { path = "../azalea-crypto", version = "^0.7.0" }
|
||||
chrono = { version = "0.4.22", default-features = false }
|
||||
base64 = "0.21.2"
|
||||
chrono = { version = "0.4.22", default-features = false, features = ["serde"] }
|
||||
log = "0.4.17"
|
||||
num-bigint = "0.4.3"
|
||||
parking_lot = "0.12.1"
|
||||
reqwest = { version = "0.11.12", default-features = false, features = [
|
||||
"json",
|
||||
"rustls-tls",
|
||||
] }
|
||||
rsa = "0.9.2"
|
||||
serde = { version = "^1.0", features = ["derive"] }
|
||||
serde_json = "1.0.93"
|
||||
thiserror = "1.0.37"
|
||||
|
|
24
azalea-auth/examples/certificates.rs
Executable file
24
azalea-auth/examples/certificates.rs
Executable file
|
@ -0,0 +1,24 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let cache_file = PathBuf::from("example_cache.json");
|
||||
|
||||
let auth_result = azalea_auth::auth(
|
||||
"example@example.com",
|
||||
azalea_auth::AuthOpts {
|
||||
cache_file: Some(cache_file),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let certs = azalea_auth::certs::fetch_certificates(&auth_result.access_token)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("{certs:?}");
|
||||
}
|
138
azalea-auth/src/certs.rs
Normal file
138
azalea-auth/src/certs.rs
Normal file
|
@ -0,0 +1,138 @@
|
|||
use base64::Engine;
|
||||
use chrono::{DateTime, Utc};
|
||||
use rsa::{pkcs8::DecodePrivateKey, RsaPrivateKey};
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FetchCertificatesError {
|
||||
#[error("Http error: {0}")]
|
||||
Http(#[from] reqwest::Error),
|
||||
#[error("Couldn't parse pkcs8 private key: {0}")]
|
||||
Pkcs8(#[from] rsa::pkcs8::Error),
|
||||
}
|
||||
|
||||
/// Fetch the Mojang-provided key-pair for your player, which is used for
|
||||
/// cryptographically signing chat messages.
|
||||
pub async fn fetch_certificates(
|
||||
minecraft_access_token: &str,
|
||||
) -> Result<Certificates, FetchCertificatesError> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let res = client
|
||||
.post("https://api.minecraftservices.com/player/certificates")
|
||||
.header("Authorization", format!("Bearer {minecraft_access_token}"))
|
||||
.send()
|
||||
.await?
|
||||
.json::<CertificatesResponse>()
|
||||
.await?;
|
||||
log::trace!("{:?}", res);
|
||||
|
||||
// using RsaPrivateKey::from_pkcs8_pem gives an error with decoding base64 so we
|
||||
// just decode it ourselves
|
||||
|
||||
// remove the first and last lines of the private key
|
||||
let private_key_pem_base64 = res
|
||||
.key_pair
|
||||
.private_key
|
||||
.lines()
|
||||
.skip(1)
|
||||
.take_while(|line| !line.starts_with('-'))
|
||||
.collect::<String>();
|
||||
let private_key_der = base64::engine::general_purpose::STANDARD
|
||||
.decode(private_key_pem_base64)
|
||||
.unwrap();
|
||||
|
||||
let public_key_pem_base64 = res
|
||||
.key_pair
|
||||
.public_key
|
||||
.lines()
|
||||
.skip(1)
|
||||
.take_while(|line| !line.starts_with('-'))
|
||||
.collect::<String>();
|
||||
let public_key_der = base64::engine::general_purpose::STANDARD
|
||||
.decode(public_key_pem_base64)
|
||||
.unwrap();
|
||||
|
||||
// the private key also contains the public key so it's basically a keypair
|
||||
let key_pair = RsaPrivateKey::from_pkcs8_der(&private_key_der).unwrap();
|
||||
|
||||
let certificates = Certificates {
|
||||
key_pair,
|
||||
key_pair_bytes: KeyPairBytes {
|
||||
private_key: private_key_der,
|
||||
public_key: public_key_der,
|
||||
},
|
||||
|
||||
signature_v1: base64::engine::general_purpose::STANDARD
|
||||
.decode(&res.public_key_signature)
|
||||
.unwrap(),
|
||||
signature_v2: base64::engine::general_purpose::STANDARD
|
||||
.decode(&res.public_key_signature_v2)
|
||||
.unwrap(),
|
||||
|
||||
expires_at: res.expires_at,
|
||||
refresh_after: res.refreshed_after,
|
||||
};
|
||||
|
||||
Ok(certificates)
|
||||
}
|
||||
|
||||
/// A chat signing certificate.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Certificates {
|
||||
/// The RSA private and public key.
|
||||
pub key_pair: RsaPrivateKey,
|
||||
/// The keypair as DER-encoded bytes.
|
||||
pub key_pair_bytes: KeyPairBytes,
|
||||
|
||||
pub signature_v1: Vec<u8>,
|
||||
pub signature_v2: Vec<u8>,
|
||||
|
||||
pub expires_at: DateTime<Utc>,
|
||||
pub refresh_after: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// A keypair as DER-encoded bytes.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KeyPairBytes {
|
||||
pub private_key: Vec<u8>,
|
||||
pub public_key: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CertificatesResponse {
|
||||
#[serde(rename = "keyPair")]
|
||||
pub key_pair: KeyPairResponse,
|
||||
|
||||
/// base64 string; signed data
|
||||
#[serde(rename = "publicKeySignature")]
|
||||
pub public_key_signature: String,
|
||||
|
||||
/// base64 string; signed data
|
||||
#[serde(rename = "publicKeySignatureV2")]
|
||||
pub public_key_signature_v2: String,
|
||||
|
||||
/// Date like `2022-04-30T00:11:32.174783069Z`
|
||||
#[serde(rename = "expiresAt")]
|
||||
pub expires_at: DateTime<Utc>,
|
||||
|
||||
/// Date like `2022-04-29T16:11:32.174783069Z`
|
||||
#[serde(rename = "refreshedAfter")]
|
||||
pub refreshed_after: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct KeyPairResponse {
|
||||
/// -----BEGIN RSA PRIVATE KEY-----
|
||||
/// ...
|
||||
/// -----END RSA PRIVATE KEY-----
|
||||
#[serde(rename = "privateKey")]
|
||||
pub private_key: String,
|
||||
|
||||
/// -----BEGIN RSA PUBLIC KEY-----
|
||||
/// ...
|
||||
/// -----END RSA PUBLIC KEY-----
|
||||
#[serde(rename = "publicKey")]
|
||||
pub public_key: String,
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
mod auth;
|
||||
mod cache;
|
||||
pub mod certs;
|
||||
pub mod game_profile;
|
||||
pub mod sessionserver;
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ anyhow = "1.0.59"
|
|||
async-trait = "0.1.58"
|
||||
azalea-auth = { path = "../azalea-auth", version = "0.7.0" }
|
||||
azalea-block = { path = "../azalea-block", version = "0.7.0" }
|
||||
azalea-nbt = { path = "../azalea-nbt", version = "0.7.0" }
|
||||
azalea-chat = { path = "../azalea-chat", version = "0.7.0" }
|
||||
azalea-core = { path = "../azalea-core", version = "0.7.0" }
|
||||
azalea-crypto = { path = "../azalea-crypto", version = "0.7.0" }
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::get_mc_dir;
|
||||
use azalea_auth::certs::{Certificates, FetchCertificatesError};
|
||||
use parking_lot::Mutex;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Something that can join Minecraft servers.
|
||||
|
@ -38,17 +40,22 @@ pub struct Account {
|
|||
pub uuid: Option<Uuid>,
|
||||
|
||||
/// The parameters (i.e. email) that were passed for creating this
|
||||
/// [`Account`]. This is used to for automatic reauthentication when we get
|
||||
/// [`Account`]. This is used for automatic reauthentication when we get
|
||||
/// "Invalid Session" errors. If you don't need that feature (like in
|
||||
/// offline mode), then you can set this to `AuthOpts::default()`.
|
||||
pub account_opts: AccountOpts,
|
||||
|
||||
/// The certificates used for chat signing.
|
||||
///
|
||||
/// This is set when you call [`Self::request_certs`], but you only
|
||||
/// need to if the servers you're joining require it.
|
||||
pub certs: Option<Certificates>,
|
||||
}
|
||||
|
||||
/// The parameters that were passed for creating the associated [`Account`].
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum AccountOpts {
|
||||
Offline { username: String },
|
||||
// this is an enum so legacy Mojang auth can be added in the future
|
||||
Microsoft { email: String },
|
||||
}
|
||||
|
||||
|
@ -64,6 +71,7 @@ impl Account {
|
|||
account_opts: AccountOpts::Offline {
|
||||
username: username.to_string(),
|
||||
},
|
||||
certs: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,6 +101,8 @@ impl Account {
|
|||
account_opts: AccountOpts::Microsoft {
|
||||
email: email.to_string(),
|
||||
},
|
||||
// we don't do chat signing by default unless the user asks for it
|
||||
certs: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -122,3 +132,29 @@ impl Account {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum RequestCertError {
|
||||
#[error("Failed to fetch certificates")]
|
||||
FetchCertificates(#[from] FetchCertificatesError),
|
||||
#[error("You can't request certificates for an offline account")]
|
||||
NoAccessToken,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
/// Request the certificates used for chat signing and set it in
|
||||
/// [`Self::certs`].
|
||||
pub async fn request_certs(&mut self) -> Result<(), RequestCertError> {
|
||||
let certs = azalea_auth::certs::fetch_certificates(
|
||||
&self
|
||||
.access_token
|
||||
.as_ref()
|
||||
.ok_or(RequestCertError::NoAccessToken)?
|
||||
.lock(),
|
||||
)
|
||||
.await?;
|
||||
self.certs = Some(certs);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use azalea_block::BlockState;
|
||||
use azalea_core::{BlockHitResult, BlockPos, Direction, GameMode, Vec3};
|
||||
use azalea_inventory::{ItemSlot, ItemSlotData};
|
||||
use azalea_nbt::NbtList;
|
||||
use azalea_physics::clip::{BlockShapeType, ClipContext, FluidPickType};
|
||||
use azalea_protocol::packets::game::{
|
||||
serverbound_interact_packet::InteractionHand,
|
||||
|
@ -20,6 +23,8 @@ use derive_more::{Deref, DerefMut};
|
|||
use log::warn;
|
||||
|
||||
use crate::{
|
||||
client::PlayerAbilities,
|
||||
inventory::InventoryComponent,
|
||||
local_player::{handle_send_packet_event, LocalGameMode},
|
||||
Client, LocalPlayer,
|
||||
};
|
||||
|
@ -193,3 +198,68 @@ pub fn pick(
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether we can't interact with the block, based on your gamemode. If
|
||||
/// this is false, then we can interact with the block.
|
||||
///
|
||||
/// Passing the inventory, block position, and instance is necessary for the
|
||||
/// adventure mode check.
|
||||
pub fn check_is_interaction_restricted(
|
||||
instance: &Instance,
|
||||
block_pos: &BlockPos,
|
||||
game_mode: &GameMode,
|
||||
inventory: &InventoryComponent,
|
||||
) -> bool {
|
||||
match game_mode {
|
||||
GameMode::Adventure => {
|
||||
// vanilla checks for abilities.mayBuild here but servers have no
|
||||
// way of modifying that
|
||||
|
||||
let held_item = inventory.held_item();
|
||||
if let ItemSlot::Present(item) = &held_item {
|
||||
let block = instance.chunks.get_block_state(block_pos);
|
||||
let Some(block) = block else {
|
||||
// block isn't loaded so just say that it is restricted
|
||||
return true;
|
||||
};
|
||||
check_block_can_be_broken_by_item_in_adventure_mode(item, &block)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
GameMode::Spectator => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the item has the `CanDestroy` tag for the block.
|
||||
pub fn check_block_can_be_broken_by_item_in_adventure_mode(
|
||||
item: &ItemSlotData,
|
||||
block: &BlockState,
|
||||
) -> bool {
|
||||
// minecraft caches the last checked block but that's kind of an unnecessary
|
||||
// optimization and makes the code too complicated
|
||||
|
||||
let Some(can_destroy) = item
|
||||
.nbt
|
||||
.as_compound()
|
||||
.and_then(|nbt| nbt.get("tag").and_then(|nbt| nbt.as_compound()))
|
||||
.and_then(|nbt| nbt.get("CanDestroy").and_then(|nbt| nbt.as_list())) else {
|
||||
// no CanDestroy tag
|
||||
return false;
|
||||
};
|
||||
|
||||
let NbtList::String(can_destroy) = can_destroy else {
|
||||
// CanDestroy tag must be a list of strings
|
||||
return false;
|
||||
};
|
||||
|
||||
return false;
|
||||
|
||||
// for block_predicate in can_destroy {
|
||||
// // TODO
|
||||
// // defined in BlockPredicateArgument.java
|
||||
// }
|
||||
|
||||
// true
|
||||
}
|
||||
|
|
|
@ -78,6 +78,9 @@ pub struct InventoryComponent {
|
|||
pub container_menu: Option<azalea_inventory::Menu>,
|
||||
/// The item that is currently held by the cursor. `Slot::Empty` if nothing
|
||||
/// is currently being held.
|
||||
///
|
||||
/// This is different from [`Self::hotbar_selected_index`], which is the
|
||||
/// item that's selected in the hotbar.
|
||||
pub carried: ItemSlot,
|
||||
/// An identifier used by the server to track client inventory desyncs. This
|
||||
/// is sent on every container click, and it's only ever updated when the
|
||||
|
@ -89,12 +92,13 @@ pub struct InventoryComponent {
|
|||
/// A set of the indexes of the slots that have been right clicked in
|
||||
/// this "quick craft".
|
||||
pub quick_craft_slots: HashSet<u16>,
|
||||
// minecraft also has these fields, but i don't
|
||||
// think they're necessary?:
|
||||
// private final NonNullList<ItemStack>
|
||||
// remoteSlots;
|
||||
// private final IntList remoteDataSlots;
|
||||
// private ItemStack remoteCarried;
|
||||
|
||||
/// The index of the item in the hotbar that's currently being held by the
|
||||
/// player. This MUST be in the range 0..9 (not including 9).
|
||||
///
|
||||
/// In a vanilla client this is changed by pressing the number keys or using
|
||||
/// the scroll wheel.
|
||||
pub selected_hotbar_slot: u8,
|
||||
}
|
||||
impl InventoryComponent {
|
||||
/// Returns a reference to the currently active menu. If a container is open
|
||||
|
@ -272,7 +276,6 @@ impl InventoryComponent {
|
|||
};
|
||||
*menu.slot_mut(slot_index as usize).unwrap() =
|
||||
ItemSlot::Present(new_carried);
|
||||
// }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -493,6 +496,13 @@ impl InventoryComponent {
|
|||
self.quick_craft_status = QuickCraftStatusKind::Start;
|
||||
self.quick_craft_slots.clear();
|
||||
}
|
||||
|
||||
/// Get the item in the player's hotbar that is currently being held.
|
||||
pub fn held_item(&self) -> ItemSlot {
|
||||
let inventory = &self.inventory_menu;
|
||||
let hotbar_items = &inventory.slots()[inventory.hotbar_slots_range()];
|
||||
hotbar_items[self.selected_hotbar_slot as usize].clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn can_item_quick_replace(
|
||||
|
@ -521,21 +531,6 @@ fn can_item_quick_replace(
|
|||
count <= item.kind.max_stack_size() as u16
|
||||
}
|
||||
|
||||
// public static void getQuickCraftSlotCount(Set<Slot> quickCraftSlots, int
|
||||
// quickCraftType, ItemStack itemStack, int var3) {
|
||||
// switch (quickCraftType) {
|
||||
// case 0:
|
||||
// itemStack.setCount(Mth.floor((float) itemStack.getCount() / (float)
|
||||
// quickCraftSlots.size())); break;
|
||||
// case 1:
|
||||
// itemStack.setCount(1);
|
||||
// break;
|
||||
// case 2:
|
||||
// itemStack.setCount(itemStack.getItem().getMaxStackSize());
|
||||
// }
|
||||
|
||||
// itemStack.grow(var3);
|
||||
// }
|
||||
fn get_quick_craft_slot_count(
|
||||
quick_craft_slots: &HashSet<u16>,
|
||||
quick_craft_kind: &QuickCraftKind,
|
||||
|
@ -561,6 +556,7 @@ impl Default for InventoryComponent {
|
|||
quick_craft_status: QuickCraftStatusKind::Start,
|
||||
quick_craft_kind: QuickCraftKind::Middle,
|
||||
quick_craft_slots: HashSet::new(),
|
||||
selected_hotbar_slot: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ mod get_mc_dir;
|
|||
pub mod interact;
|
||||
pub mod inventory;
|
||||
mod local_player;
|
||||
mod mining;
|
||||
mod movement;
|
||||
pub mod packet_handling;
|
||||
pub mod ping;
|
||||
|
|
35
azalea-client/src/mining.rs
Normal file
35
azalea-client/src/mining.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use azalea_core::BlockPos;
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
use crate::Client;
|
||||
|
||||
/// A plugin that allows clients to break blocks in the world.
|
||||
pub struct MinePlugin;
|
||||
impl Plugin for MinePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_event::<StartMiningBlockEvent>()
|
||||
.add_system(handle_start_mining_block_event);
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Start mining a block.
|
||||
pub fn start_mining_block(&self, position: BlockPos) {
|
||||
self.ecs.lock().send_event(StartMiningBlockEvent {
|
||||
entity: self.entity,
|
||||
position,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StartMiningBlockEvent {
|
||||
pub entity: Entity,
|
||||
pub position: BlockPos,
|
||||
}
|
||||
|
||||
fn handle_start_mining_block_event(mut events: EventReader<StartMiningBlockEvent>) {
|
||||
for event in events.iter() {
|
||||
//
|
||||
}
|
||||
}
|
|
@ -82,6 +82,15 @@ impl GameMode {
|
|||
}
|
||||
}
|
||||
|
||||
impl GameMode {
|
||||
/// Whether the player can't interact with blocks while in this game mode.
|
||||
///
|
||||
/// (Returns true if you're in adventure or spectator.)
|
||||
pub fn is_block_placing_restricted(&self) -> bool {
|
||||
matches!(self, GameMode::Adventure | GameMode::Spectator)
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for GameMode {
|
||||
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
let id = u8::read_from(buf)?;
|
||||
|
|
|
@ -177,6 +177,10 @@ pub fn generate(input: &DeclareMenus) -> TokenStream {
|
|||
}
|
||||
|
||||
/// Get the range of slot indexes that contain the player's hotbar. This may be different for each menu.
|
||||
///
|
||||
/// ```
|
||||
/// let hotbar_items = &inventory.slots()[inventory.hotbar_slots_range()];
|
||||
/// ```
|
||||
pub fn hotbar_slots_range(&self) -> RangeInclusive<usize> {
|
||||
// hotbar is always last 9 slots in the player's inventory
|
||||
((*self.player_slots_range().end() - 8)..=*self.player_slots_range().end())
|
||||
|
|
Loading…
Reference in a new issue