~starkingdoms/starkingdoms

ac6c25d8f4ede59821853002db1e8fb3e6608e6b — ghostlyzsh 1 year, 11 months ago 7a60996 + d16973b
Merge branch 'bevy_rewrite' of https://gitlab.com/starkingdoms.tk/starkingdoms.tk into bevy_rewrite
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
@@ 97,3 97,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
@@ 26,6 26,7 @@ use component::Input;
use component::*;
use packet::*;
use rand::Rng;
use starkingdoms_common::unpack_savefile;
use std::f32::consts::PI;

pub mod component;


@@ 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,


@@ 234,6 239,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) {


@@ 242,7 248,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/components/ui/Popup.svelte => starkingdoms-client/src/components/ui/Popup.svelte +65 -14
@@ 4,6 4,7 @@
  import ChevronDown from "../../icons/ChevronDown.svelte";
  import ChevronUp from "../../icons/ChevronUp.svelte";
  import Button from "./Button.svelte";
  import createDebug from "debug";

  let clazz = "";
  export { clazz as class };


@@ 28,11 29,17 @@

  onMount(() => {
    if (draggable) {
      let top = window.localStorage.getItem(`pop-${id}top`);
      let left = window.localStorage.getItem(`pop-${id}left`);
      pos1 = parseInt(window.localStorage.getItem(`pop-${id}top`));
      pos2 = parseInt(window.localStorage.getItem(`pop-${id}left`));

      popup.style.top = top;
      popup.style.left = left;
      // Correct illegally placed draggables
      // Disabled until I can find a nice way to run this after the dynamic
      // content injection by svelte
      //[pos1, pos2] = dragBoundsEnforce();

      // I hate JS for letting me pull crap like this
      popup.style.top = `${pos1}px`;
      popup.style.left = `${pos2}px`;
    }
    if (minimizable) {
      minimized = window.localStorage.getItem(`pop-${id}minim`) === "yes";


@@ 43,23 50,67 @@
    if (!draggable) {
      return;
    }
    pos3 = e.clientX;
    pos4 = e.clientY;

    let popup_dimensions = popup.getBoundingClientRect();

    pos3 = e.clientX - popup_dimensions.left;
    pos4 = e.clientY - popup_dimensions.top;
    document.onmouseup = closeDragElement;
    document.onmousemove = elementDrag;
  }

  function elementDrag(e) {
    e.preventDefault();
    pos1 = pos3 - e.clientX;
    pos2 = pos4 - e.clientY;
    pos3 = e.clientX;
    pos4 = e.clientY;
    popup.style.top = popup.offsetTop - pos2 + "px";
    popup.style.left = popup.offsetLeft - pos1 + "px";

    window.localStorage.setItem(`pop-${id}top`, popup.style.top);
    window.localStorage.setItem(`pop-${id}left`, popup.style.left);
    pos1 = e.clientX - pos3;
    pos2 = e.clientY - pos4;

    let [left, top, bounds_violated] = dragBoundsEnforce();

    popup.style.left = left + "px";
    popup.style.top = top + "px";
  }

  function dragBoundsEnforce() {
    let left = pos1;
    let top = pos2;

    let popup_dimensions = popup.getBoundingClientRect();

    // This was originally meant to specify the amount of error acceptable
    // before snapping to an edge or corner, but now it seems to merely adjust
    // the amount of pixels between the screen edge and the bounds.
    let snap_distance = 16;
    let bounds = {
      top: 8 + snap_distance,
      left: 8 + snap_distance,
      bottom: window.innerHeight - popup_dimensions.height - 8 - snap_distance,
      right: window.innerWidth - popup_dimensions.width - 8 - snap_distance,
    };

    let bounds_violated = false;

    // Add cases for snapping here
    if (left >= bounds.right) {
      // Too far right!
      left = bounds.right;
      bounds_violated = true;
    } else if (left <= bounds.left) {
      // Too far left!
      left = bounds.left;
      bounds_violated = true;
    }
    if (top >= bounds.bottom) {
      // Too far down!
      top = bounds.bottom;
      bounds_violated = true;
    } else if (top <= bounds.top) {
      // Too far up!
      top = bounds.top;
      bounds_violated = true;
    }

    return [left, top, bounds_violated];
  }

  function closeDragElement() {

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/icons/MovableIcon.svelte => starkingdoms-client/src/icons/MovableIcon.svelte +40 -9
@@ 4,14 4,45 @@
</script>

<svg
  width="1rem"
  height="1rem"
  viewBox="0 0 32 32"
  version="1.1"
  id="svg1"
  xmlns="http://www.w3.org/2000/svg"
  fill="none"
  viewBox="0 0 24 24"
  stroke-width="1.5"
  stroke="currentColor"
  class={clazz}>
  <path
    stroke-linecap="round"
    stroke-linejoin="round"
    d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" />
  xmlns:svg="http://www.w3.org/2000/svg">
  <defs id="defs1" />
  <g id="layer1">
    <path
      id="rect1-5"
      style="fill:#ffffff;fill-opacity:1;stroke-width:0"
      d="M 6,22 0,16 6,10 Z" />
    <path
      id="rect1-5-6"
      style="fill:#ffffff;fill-opacity:1;stroke-width:0"
      d="M 10,6 16,0 22,6 Z" />
    <path
      id="rect1-5-61"
      style="fill:#ffffff;fill-opacity:1;stroke-width:0"
      d="M 26,10 32,16 26,22 Z" />
    <path
      id="rect1-5-2"
      style="fill:#ffffff;fill-opacity:1;stroke-width:0"
      d="M 22,26 16,32 10,26 Z" />
    <rect
      style="fill:#ffffff;fill-opacity:1;stroke-width:0"
      id="rect1"
      width="4"
      height="24"
      x="14"
      y="4" />
    <rect
      style="fill:#ffffff;fill-opacity:1;stroke-width:0"
      id="rect1-7"
      width="4"
      height="24"
      x="14"
      y="-28"
      transform="rotate(90)" />
  </g>
</svg>

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)
}