1use anyhow::{Error, Result};
2use lazy_static::lazy_static;
3use serde::de::Visitor;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use serde_json::Value;
6use std::collections::hash_map::DefaultHasher;
7use std::convert::{TryFrom, TryInto};
8use std::fmt::{Debug, Display, Formatter};
9use std::hash::{Hash, Hasher};
10
11pub type HashMap<K, V> = std::collections::HashMap<K, V, fnv::FnvBuildHasher>;
12pub type HashSet<V> = std::collections::HashSet<V, fnv::FnvBuildHasher>;
13
14#[derive(PartialEq, Clone, Default, Debug)]
18pub struct ScratchFile {
19 pub project: Project,
20
21 pub images: HashMap<String, Image>,
23}
24
25impl ScratchFile {
26 pub fn parse<R>(file: R) -> Result<ScratchFile>
28 where
29 R: std::io::Read + std::io::Seek,
30 {
31 use std::io::Read;
32
33 let mut archive = zip::ZipArchive::new(file)?;
34 let project: Project = serde_json::from_reader(archive.by_name("project.json")?)?;
35
36 let mut image_names: Vec<String> = Vec::new();
37 for name in archive.file_names() {
38 if name.ends_with(".svg") | name.ends_with(".png") {
39 image_names.push(name.to_string());
40 }
41 }
42
43 let mut images: HashMap<String, Image> = HashMap::default();
44 for name in &image_names {
45 let mut b: Vec<u8> = Vec::new();
46 archive.by_name(name).unwrap().read_to_end(&mut b)?;
47 let image = if name.ends_with(".svg") {
48 Image::SVG(b)
49 } else if name.ends_with(".png") {
50 Image::PNG(b)
51 } else {
52 return Err(Error::msg("unrecognized file extension"));
53 };
54 images.insert(name.clone(), image);
55 }
56
57 Ok(Self { project, images })
58 }
59}
60
61#[derive(PartialEq, Clone, Default, Debug, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase")]
63pub struct Project {
64 pub targets: Vec<Target>,
65 pub monitors: Vec<Monitor>,
66 pub extensions: Vec<String>,
67 pub meta: Meta,
68}
69
70#[derive(Clone, Debug, Serialize, Deserialize)]
72#[serde(rename_all = "camelCase")]
73pub struct Target {
74 pub is_stage: bool,
76 pub name: String,
77 pub variables: HashMap<String, Variable>,
78 pub blocks: HashMap<BlockID, Block>,
79 pub costumes: Vec<Costume>,
80 #[serde(default)]
82 pub layer_order: usize,
83 #[serde(default)]
86 pub x: f64,
87 #[serde(default)]
89 pub y: f64,
90 #[serde(default)]
91 pub size: f64,
92 #[serde(default)]
93 pub visible: bool,
94}
95
96impl Default for Target {
97 fn default() -> Self {
98 Self {
99 is_stage: false,
100 name: String::new(),
101 variables: HashMap::default(),
102 blocks: HashMap::default(),
103 costumes: Vec::new(),
104 layer_order: 0,
105 x: 0.0,
106 y: 0.0,
107 size: 0.0,
108 visible: true,
109 }
110 }
111}
112
113impl Hash for Target {
114 fn hash<H: Hasher>(&self, state: &mut H) {
115 self.is_stage.hash(state);
116 self.name.hash(state);
117 sorted_entries(&self.variables).hash(state);
118 sorted_entries(&self.blocks).hash(state);
119 self.costumes.hash(state);
120 self.x.to_bits().hash(state);
121 self.y.to_bits().hash(state);
122 self.size.to_bits().hash(state);
123 }
124}
125
126impl PartialEq for Target {
127 fn eq(&self, other: &Self) -> bool {
128 equal_hash(&self, other)
129 }
130}
131
132#[derive(Clone, Default, Debug, Serialize, Deserialize)]
133#[serde(rename_all = "camelCase")]
134pub struct Variable {
135 pub id: String,
136 pub value: Value,
137 #[serde(default)]
138 pub i_dont_know_what_this_does: bool,
139}
140
141impl Hash for Variable {
142 fn hash<H: Hasher>(&self, state: &mut H) {
143 self.id.hash(state);
144 hash_value(&self.value, state);
145 self.i_dont_know_what_this_does.hash(state);
146 }
147}
148
149impl PartialEq for Variable {
150 fn eq(&self, other: &Self) -> bool {
151 equal_hash(&self, other)
152 }
153}
154
155fn equal_hash<A, B>(a: A, b: B) -> bool
156where
157 A: Hash,
158 B: Hash,
159{
160 let mut hasher_a = DefaultHasher::new();
161 a.hash(&mut hasher_a);
162 let mut hasher_b = DefaultHasher::new();
163 b.hash(&mut hasher_b);
164 hasher_a.finish() == hasher_b.finish()
165}
166
167#[derive(Clone, Default, Debug, Serialize, Deserialize)]
169#[serde(rename_all = "camelCase")]
170pub struct Block {
171 pub opcode: String,
172 pub next: Option<BlockID>,
174 pub inputs: HashMap<String, Value>,
176 pub fields: HashMap<String, Vec<Option<String>>>,
178 pub top_level: bool,
180}
181
182impl Hash for Block {
183 fn hash<H: Hasher>(&self, state: &mut H) {
184 self.opcode.hash(state);
185 self.next.hash(state);
186
187 for entry in sorted_entries(&self.inputs) {
188 entry.0.hash(state);
189 hash_value(&entry.1, state);
190 }
191
192 sorted_entries(&self.fields).hash(state);
193
194 self.top_level.hash(state);
195 }
196}
197
198impl PartialEq for Block {
199 fn eq(&self, other: &Self) -> bool {
200 equal_hash(&self, other)
201 }
202}
203
204fn sorted_entries<K, V>(map: &HashMap<K, V>) -> Vec<(&K, &V)>
205where
206 K: std::cmp::Ord,
207{
208 let mut result: Vec<(&K, &V)> = map.iter().collect();
209 result.sort_unstable_by(|a, b| a.0.cmp(b.0));
210 result
211}
212
213fn hash_value<H>(value: &Value, state: &mut H)
214where
215 H: Hasher,
216{
217 value.to_string().hash(state)
218}
219
220#[derive(Clone, Debug, Serialize, Deserialize)]
222#[serde(rename_all = "camelCase")]
223pub struct Costume {
224 pub name: String,
225 pub md5ext: Option<String>,
226 pub asset_id: String,
227 pub rotation_center_x: f64,
229 pub rotation_center_y: f64,
230 #[serde(default)]
231 pub bitmap_resolution: f64,
232}
233
234impl Default for Costume {
235 fn default() -> Self {
236 Self {
237 name: String::new(),
238 md5ext: None,
239 asset_id: String::new(),
240 rotation_center_x: 0.0,
241 rotation_center_y: 0.0,
242 bitmap_resolution: 1.0,
243 }
244 }
245}
246
247impl Hash for Costume {
248 fn hash<H: Hasher>(&self, state: &mut H) {
249 self.name.hash(state);
250 self.md5ext.hash(state);
251 self.rotation_center_x.to_bits().hash(state);
252 self.rotation_center_y.to_bits().hash(state);
253 }
254}
255
256impl PartialEq for Costume {
257 fn eq(&self, other: &Self) -> bool {
258 equal_hash(&self, other)
259 }
260}
261
262#[derive(PartialEq, Clone, Default, Debug, Serialize, Deserialize)]
264#[serde(rename_all = "camelCase")]
265pub struct Monitor {
266 pub id: String,
267 pub mode: String,
268 pub opcode: String,
269 pub params: MonitorParams,
270 pub sprite_name: Option<String>,
271 pub value: Value,
272 pub x: f64,
273 pub y: f64,
274 pub visible: bool,
275 pub slider_min: f64,
276 pub slider_max: f64,
277 pub is_discrete: bool,
278}
279
280#[derive(PartialEq, Clone, Default, Debug, Serialize, Deserialize)]
281#[serde(rename_all = "camelCase")]
282pub struct MonitorParams {
283 #[serde(rename = "VARIABLE")]
284 pub variable: String,
285}
286
287#[derive(PartialEq, Clone, Default, Debug, Serialize, Deserialize)]
288#[serde(rename_all = "camelCase")]
289pub struct Meta {
290 pub semver: String,
291 pub vm: String,
292 pub agent: String,
293}
294
295#[derive(PartialEq, Eq, Clone)]
297pub enum Image {
298 SVG(Vec<u8>),
299 PNG(Vec<u8>),
300}
301
302impl Debug for Image {
303 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
304 let vec_len = match self {
305 Image::SVG(v) => {
306 write!(f, "SVG(")?;
307 v.len()
308 }
309 Image::PNG(v) => {
310 write!(f, "PNG(")?;
311 v.len()
312 }
313 };
314
315 if vec_len > 0 {
316 write!(f, "[...]")?;
317 } else {
318 write!(f, "[]")?;
319 }
320
321 write!(f, ")")
322 }
323}
324
325#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Default, Hash)]
327pub struct BlockID {
328 id: [u8; 20],
329}
330
331lazy_static! {
332 static ref PSEUDO_ID: BlockID = BlockID::try_from(" ").unwrap();
333}
334
335impl BlockID {
336 pub fn new(id: [u8; 20]) -> Self {
337 Self { id }
338 }
339
340 pub fn pseudo_id() -> BlockID {
342 *PSEUDO_ID
343 }
344}
345
346impl Debug for BlockID {
347 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
348 f.write_str("BlockID { ")?;
349 Display::fmt(self, f)?;
350 f.write_str(" }")
351 }
352}
353
354impl Display for BlockID {
355 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
356 f.write_str(std::str::from_utf8(&self.id[..10]).map_err(|_| std::fmt::Error {})?)
357 }
358}
359
360impl TryFrom<&str> for BlockID {
361 type Error = Error;
362
363 fn try_from(s: &str) -> Result<Self> {
364 let mut id: [u8; 20] = [0; 20];
365 let s_bytes = s.as_bytes();
366 if s_bytes.len() == id.len() {
367 id.copy_from_slice(s_bytes);
368 Ok(Self { id })
369 } else {
370 Err(Error::msg("invalid string"))
371 }
372 }
373}
374
375impl Serialize for BlockID {
376 fn serialize<S>(
377 &self,
378 serializer: S,
379 ) -> std::result::Result<<S as Serializer>::Ok, <S as Serializer>::Error>
380 where
381 S: Serializer,
382 {
383 serializer.serialize_str(&self.to_string())
384 }
385}
386
387impl<'de> Deserialize<'de> for BlockID {
388 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, <D as Deserializer<'de>>::Error>
389 where
390 D: Deserializer<'de>,
391 {
392 struct BytesVisitor;
393
394 impl<'de> Visitor<'de> for BytesVisitor {
395 type Value = BlockID;
396
397 fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
398 formatter.write_str("string")
399 }
400
401 fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
402 where
403 E: serde::de::Error,
404 {
405 v.try_into().map_err(serde::de::Error::custom)
406 }
407 }
408
409 deserializer.deserialize_str(BytesVisitor)
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416
417 #[test]
418 fn test_savefile() {
419 let file = std::fs::File::open("test_saves/say.sb3").unwrap();
420 let savefile = ScratchFile::parse(&file).unwrap();
421 let target = &savefile.project.targets[1];
422 assert_eq!(target.name, "Sprite1");
423 }
424
425 mod block_id {
426 use super::*;
427
428 #[test]
429 fn test_from_str() {
430 {
431 assert!(BlockID::try_from("").is_err());
432 }
433 {
434 assert!(BlockID::try_from("a").is_err());
435 }
436 {
437 let s = "G@pZX]3ynBGB)L`_LJk8";
438 let id = BlockID::try_from(s).unwrap();
439 assert_eq!(&id.to_string(), "G@pZX]3ynB");
440 }
441 }
442 }
443}