~starkingdoms/starkingdoms

6d653a9843614d9a6bf881fc1b3b9514f66540b7 — core 1 year, 11 months ago 12dc42a
server save infrastructure
M Cargo.lock => Cargo.lock +26 -0
@@ 3236,6 3236,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b"

[[package]]
name = "rmp"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20"
dependencies = [
 "byteorder",
 "num-traits",
 "paste",
]

[[package]]
name = "rmp-serde"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a"
dependencies = [
 "byteorder",
 "rmp",
 "serde",
]

[[package]]
name = "robust"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3549,7 3571,11 @@ dependencies = [
name = "starkingdoms-common"
version = "0.1.0"
dependencies = [
 "base64 0.21.5",
 "hmac",
 "rmp-serde",
 "serde",
 "sha2",
]

[[package]]

M server/src/component.rs => server/src/component.rs +5 -0
@@ 96,3 96,8 @@ impl Default for ModuleTimer {
        Self::new()
    }
}

#[derive(Resource)]
pub struct AppKeys {
    pub app_key: String,
}

M server/src/main.rs => server/src/main.rs +25 -1
@@ 27,6 27,7 @@ use component::Input;
use component::*;
use packet::*;
use rand::Rng;
use starkingdoms_common::unpack_savefile;

pub mod component;
pub mod macros;


@@ 53,7 54,11 @@ const LANDING_THRUSTER_FORCE: f32 = 5.;
const FREE_MODULE_CAP: usize = 30;

fn main() {
    // read the key in
    let key = std::fs::read_to_string("/etc/starkingdoms/app_key").unwrap();

    App::new()
        .insert_resource(AppKeys { app_key: key })
        .insert_resource(TwiteServerConfig {
            addr: Ipv4Addr::new(0, 0, 0, 0),
            port: 3000,


@@ 225,6 230,7 @@ fn on_message(
    >,
    mut packet_recv: Local<ManualEventReader<ServerEvent>>,
    mut packet_event_send: ResMut<Events<ServerEvent>>,
    app_keys: Res<AppKeys>,
) {
    let mut event_queue = Vec::new();
    for ev in packet_recv.read(&packet_event_send) {


@@ 233,7 239,25 @@ fn on_message(
            let packet: Packet = err_or_cont!(serde_json::from_str(&data));

            match packet {
                Packet::ClientLogin { username, .. } => {
                Packet::ClientLogin { username, save } => {
                    if let Some(save) = save {
                        // attempt to decode the savefile
                        if let Ok(savefile) = unpack_savefile(&app_keys.app_key, save) {
                            // HEY! GHOSTLY! THIS SAVE FILE IS VALID! PLEASE LOAD IT!
                            // THANKS!
                        } else {
                            let packet = Packet::Message {
                                message_type: packet::MessageType::Error,
                                actor: "SERVER".to_string(),
                                content: "Savefile signature corrupted or inner data invalid. Save was not loaded. Contact StarKingdoms staff for assistance.".to_string(),
                            };
                            let buf = serde_json::to_vec(&packet).unwrap();
                            event_queue.push(ServerEvent::Send(*addr, MessageType::Text, buf));
                        }
                    } else {
                        // nothing to do
                    }

                    let angle: f32 = {
                        let mut rng = rand::thread_rng();
                        rng.gen::<f32>() * std::f32::consts::PI * 2.

M server/src/packet.rs => server/src/packet.rs +1 -1
@@ 65,7 65,7 @@ pub enum Packet {
    // serverbound
    ClientLogin {
        username: String,
        jwt: Option<String>,
        save: Option<String>,
    },
    SendMessage {
        target: Option<String>,

M starkingdoms-client/src/hub.ts => starkingdoms-client/src/hub.ts +1 -1
@@ 56,7 56,7 @@ export async function hub_connect(
      t: PacketType.ClientLogin,
      c: {
        username,
        jwt: null,
        save: null,
      },
    };
    sendPacket(client, packet);

M starkingdoms-client/src/protocol.ts => starkingdoms-client/src/protocol.ts +1 -1
@@ 31,7 31,7 @@ export interface Part {
}
export interface ClientLoginPacket {
  username: string;
  jwt: string | null;
  save: string | null;
}
export interface SpawnPlayerPacket {
  id: number;

M starkingdoms-common/Cargo.toml => starkingdoms-common/Cargo.toml +5 -1
@@ 6,4 6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde = { version = "1", features = ["derive"] }
\ No newline at end of file
serde = { version = "1", features = ["derive"] }
rmp-serde = "1"
hmac = "0.12"
sha2 = "0.10"
base64 = "0.21"
\ No newline at end of file

M starkingdoms-common/src/lib.rs => starkingdoms-common/src/lib.rs +67 -4
@@ 1,7 1,70 @@
// StarKingdoms.IO, a browser game about drifting through space
//     Copyright (C) 2024 ghostly_zsh, TerraMaster85, core
//
//     This program is free software: you can redistribute it and/or modify
//     it under the terms of the GNU Affero General Public License as published by
//     the Free Software Foundation, either version 3 of the License, or
//     (at your option) any later version.
//
//     This program is distributed in the hope that it will be useful,
//     but WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//     GNU Affero General Public License for more details.
//
//     You should have received a copy of the GNU Affero General Public License
//     along with this program.  If not, see <https://www.gnu.org/licenses/>.

use base64::Engine;
use hmac::{Hmac, Mac};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use std::error::Error;

#[derive(Serialize, Deserialize)]
pub struct SaveData {
    // ----------------------------------------------------------------------
    // HEY YOU
    // YES YOU
    // GHOSTLY
    // FILL THIS WITH STUFF
    // ----------------------------------------------------------------------
    // THANKS! -core
}

// no touchy. this is the struct that savefiles are actually represented in
#[derive(Serialize, Deserialize)]
pub struct Savefile {
    data_msgpack: Vec<u8>,
    mac: Vec<u8>,
}

pub fn pack_savefile(key: &str, save_data: SaveData) -> String {
    let mut mac: Hmac<Sha256> = Hmac::new_from_slice(key.as_bytes()).unwrap();

    let save_data_bytes = rmp_serde::to_vec(&save_data).unwrap();
    mac.update(&save_data_bytes);
    let mc_code = mac.finalize().into_bytes();

    let save_file = Savefile {
        data_msgpack: save_data_bytes,
        mac: mc_code.to_vec(),
    };

    let final_bytes = rmp_serde::to_vec(&save_file).unwrap();

    base64::engine::general_purpose::STANDARD.encode(final_bytes)
}
pub fn unpack_savefile(key: &str, file: String) -> Result<SaveData, Box<dyn Error>> {
    // << reverse! <<
    let savefile_bytes = base64::engine::general_purpose::STANDARD.decode(file)?;

    let save_file: Savefile = rmp_serde::from_slice(&savefile_bytes)?;

    let mut mac: Hmac<Sha256> = Hmac::new_from_slice(key.as_bytes()).unwrap();
    mac.update(&save_file.data_msgpack);
    mac.verify_slice(&save_file.mac)?;

    let save_data = rmp_serde::from_slice(&save_file.data_msgpack)?;

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(tag = "version", content = "data")]
pub enum PlayerSaveFile {
    V1 {},
    Ok(save_data)
}