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