diff --git a/benches/encoding.rs b/benches/encoding.rs
index 03bc40c819fce1e727216cfc0bdf175d324c0284..6e0fc43649f706d421b64d722f87d06642d4d55a 100644
--- a/benches/encoding.rs
+++ b/benches/encoding.rs
@@ -28,7 +28,7 @@ fn benchmark_encoding(c: &mut Criterion) {
 
     group.bench_function("encode_zippy", |b| {
         b.iter(|| {
-            let encoded = encode_zippy(black_box(&data), None).unwrap();
+            let encoded = encode_zippy(black_box(&data), None, None).unwrap();
             black_box(encoded);
         })
     });
@@ -40,7 +40,7 @@ fn benchmark_decoding(c: &mut Criterion) {
     let data = generate_data(10_000_000_usize);
     let encoded_huffman = encode_huffman(black_box(&data)).unwrap();
     let encoded_rle = encode_rle(black_box(&data));
-    let encoded_zippy = encode_zippy(black_box(&data), None).unwrap();
+    let encoded_zippy = encode_zippy(black_box(&data), None, None).unwrap();
 
     let mut group = c.benchmark_group("decoding");
     group.throughput(Throughput::Bytes(data.len() as u64));
diff --git a/crates/common/src/error.rs b/crates/common/src/error.rs
index 52d9b050bc3a7aa6a9a6896c75478f8af9e56f64..a23b54d32593055f14f9f23c4feeb1e48cd403d2 100644
--- a/crates/common/src/error.rs
+++ b/crates/common/src/error.rs
@@ -18,8 +18,11 @@ use std::{
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum Error {
     InvalidData,
+    InvalidKey,
     RomSize,
     IncompatibleBootRom,
+    MissingOption(String),
+    IoError(String),
     InvalidParameter(String),
     CustomError(String),
 }
@@ -28,8 +31,11 @@ impl Error {
     pub fn description(&self) -> String {
         match self {
             Error::InvalidData => String::from("Invalid data format"),
+            Error::InvalidKey => String::from("Invalid key"),
             Error::RomSize => String::from("Invalid ROM size"),
             Error::IncompatibleBootRom => String::from("Incompatible Boot ROM"),
+            Error::MissingOption(option) => format!("Missing option: {}", option),
+            Error::IoError(message) => format!("IO error: {}", message),
             Error::InvalidParameter(message) => format!("Invalid parameter: {}", message),
             Error::CustomError(message) => String::from(message),
         }
@@ -43,8 +49,8 @@ impl Display for Error {
 }
 
 impl From<io::Error> for Error {
-    fn from(_error: io::Error) -> Self {
-        Error::CustomError(String::from("IO error"))
+    fn from(error: io::Error) -> Self {
+        Error::IoError(error.to_string())
     }
 }
 
diff --git a/crates/encoding/src/lib.rs b/crates/encoding/src/lib.rs
index 0998974ceda3eb3742485787095e7edd4b6f34f9..5e53ad4a885b3a16f59062e9fb30bcfac99f65ac 100644
--- a/crates/encoding/src/lib.rs
+++ b/crates/encoding/src/lib.rs
@@ -1,3 +1,4 @@
 pub mod huffman;
+pub mod rc4;
 pub mod rle;
 pub mod zippy;
diff --git a/crates/encoding/src/rc4.rs b/crates/encoding/src/rc4.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b98fe127b406a9627f48061e67ae2902b6f0b197
--- /dev/null
+++ b/crates/encoding/src/rc4.rs
@@ -0,0 +1,122 @@
+pub struct Rc4 {
+    s: [u8; 256],
+    i: u8,
+    j: u8,
+}
+
+impl Rc4 {
+    pub fn new(key: &[u8]) -> Self {
+        let mut s: [u8; 256] = [0; 256];
+        for (i, v) in s.iter_mut().enumerate() {
+            *v = i as u8;
+        }
+
+        let key_len = key.len();
+        if key_len > 0 {
+            let mut j = 0;
+            for i in 0..256 {
+                j = (j + s[i] as usize + key[i % key_len] as usize) % 256;
+                s.swap(i, j);
+            }
+        }
+
+        Rc4 { s, i: 0, j: 0 }
+    }
+
+    pub fn process(&mut self, data: &mut [u8]) {
+        for byte in data.iter_mut() {
+            self.i = self.i.wrapping_add(1);
+            self.j = self.j.wrapping_add(self.s[self.i as usize]);
+            self.s.swap(self.i as usize, self.j as usize);
+            let k =
+                self.s[(self.s[self.i as usize].wrapping_add(self.s[self.j as usize])) as usize];
+            *byte ^= k;
+        }
+    }
+}
+
+pub fn rc4_encrypt(key: &[u8], data: &mut [u8]) {
+    let mut rc4 = Rc4::new(key);
+    rc4.process(data);
+}
+
+pub fn rc4_decrypt(key: &[u8], data: &mut [u8]) {
+    rc4_encrypt(key, data)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::Rc4;
+
+    #[test]
+    fn test_rc4_initialization() {
+        let key = b"key";
+        let rc4 = Rc4::new(key);
+        assert_eq!(rc4.s.len(), 256);
+    }
+
+    #[test]
+    fn test_rc4_encryption_decryption() {
+        let key = b"supersecretkey";
+        let plaintext = b"hello world";
+        let mut data = plaintext.to_vec();
+
+        let mut rc4 = Rc4::new(key);
+        rc4.process(&mut data);
+        assert_ne!(&data, plaintext);
+
+        let mut rc4 = Rc4::new(key);
+        rc4.process(&mut data);
+        assert_eq!(&data, plaintext);
+    }
+
+    #[test]
+    fn test_rc4_empty_key() {
+        let key = b"";
+        let mut data = b"hello world".to_vec();
+
+        let mut rc4 = Rc4::new(key);
+        rc4.process(&mut data);
+
+        let mut rc4 = Rc4::new(key);
+        rc4.process(&mut data);
+        assert_eq!(data, b"hello world");
+    }
+
+    #[test]
+    fn test_rc4_empty_data() {
+        let key = b"supersecretkey";
+        let mut data: Vec<u8> = vec![];
+
+        let mut rc4 = Rc4::new(key);
+        rc4.process(&mut data);
+
+        let mut rc4 = Rc4::new(key);
+        rc4.process(&mut data);
+        assert!(data.is_empty());
+    }
+
+    #[test]
+    fn test_rc4_with_different_keys() {
+        let key1 = b"key1";
+        let key2 = b"key2";
+        let plaintext = b"hello world";
+        let mut data1 = plaintext.to_vec();
+        let mut data2 = plaintext.to_vec();
+
+        let mut rc4 = Rc4::new(key1);
+        rc4.process(&mut data1);
+
+        let mut rc4 = Rc4::new(key2);
+        rc4.process(&mut data2);
+        assert_ne!(data1, data2);
+
+        let mut rc4 = Rc4::new(key1);
+        rc4.process(&mut data1);
+
+        let mut rc4 = Rc4::new(key2);
+        rc4.process(&mut data2);
+        assert_eq!(data1, plaintext);
+        assert_eq!(data2, plaintext);
+    }
+}
diff --git a/crates/encoding/src/zippy.rs b/crates/encoding/src/zippy.rs
index 5de497487477c59bc55d8bef3d95ace03502b234..d109f75f58dfe8875c71884b7431a62d9b7f5ba6 100644
--- a/crates/encoding/src/zippy.rs
+++ b/crates/encoding/src/zippy.rs
@@ -13,6 +13,7 @@ use boytacean_hashing::crc32c::crc32c;
 
 use crate::{
     huffman::{decode_huffman, encode_huffman},
+    rc4::{rc4_decrypt, rc4_encrypt},
     rle::{decode_rle, encode_rle},
 };
 
@@ -20,18 +21,30 @@ pub const ZIPPY_MAGIC: &str = "ZIPY";
 
 pub const ZIPPY_MAGIC_UINT: u32 = 0x5a495059;
 
+pub const ZIPPY_CYPHER_TEST: &[u8; 22] = b"ZIPPY_CYPHER_SIGNATURE";
+
 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
 pub enum ZippyFeatures {
     Crc32,
-    Encrypted,
+    EncryptedRc4,
     Other,
 }
 
+impl From<ZippyFeatures> for &str {
+    fn from(value: ZippyFeatures) -> Self {
+        match value {
+            ZippyFeatures::Crc32 => "crc32",
+            ZippyFeatures::EncryptedRc4 => "encrypted_rc4",
+            ZippyFeatures::Other => "other",
+        }
+    }
+}
+
 impl From<&ZippyFeatures> for &str {
     fn from(value: &ZippyFeatures) -> Self {
         match value {
             ZippyFeatures::Crc32 => "crc32",
-            ZippyFeatures::Encrypted => "encrypted",
+            ZippyFeatures::EncryptedRc4 => "encrypted_rc4",
             ZippyFeatures::Other => "other",
         }
     }
@@ -41,7 +54,7 @@ impl From<u32> for ZippyFeatures {
     fn from(value: u32) -> Self {
         match value {
             0 => Self::Crc32,
-            1 => Self::Encrypted,
+            1 => Self::EncryptedRc4,
             _ => Self::Other,
         }
     }
@@ -51,7 +64,7 @@ impl From<&str> for ZippyFeatures {
     fn from(value: &str) -> Self {
         match value {
             "crc32" => Self::Crc32,
-            "encrypted" => Self::Encrypted,
+            "encrypted_rc4" => Self::EncryptedRc4,
             _ => Self::Other,
         }
     }
@@ -62,23 +75,28 @@ pub struct Zippy {
     name: String,
     description: String,
     features: HashSet<ZippyFeatures>,
+    options: ZippyOptions,
     crc32: u32,
     data: Vec<u8>,
 }
 
 pub struct ZippyOptions {
     crc32: bool,
+    key: Option<String>,
 }
 
 impl ZippyOptions {
-    pub fn new(crc32: bool) -> Self {
-        Self { crc32 }
+    pub fn new(crc32: bool, key: Option<String>) -> Self {
+        Self { crc32, key }
     }
 }
 
 impl default::Default for ZippyOptions {
     fn default() -> Self {
-        Self { crc32: true }
+        Self {
+            crc32: true,
+            key: None,
+        }
     }
 }
 
@@ -92,15 +110,13 @@ impl Zippy {
     ) -> Result<Self, Error> {
         let features = features.unwrap_or(vec![ZippyFeatures::Crc32]);
         let options = options.unwrap_or_default();
+        let is_crc32 = options.crc32;
         Ok(Self {
             name,
             description,
             features: HashSet::from_iter(features.iter().cloned()),
-            crc32: if options.crc32 {
-                crc32c(data)
-            } else {
-                0xffffffff
-            },
+            options,
+            crc32: if is_crc32 { crc32c(data) } else { 0xffffffff },
             data: data.to_vec(),
         })
     }
@@ -115,7 +131,9 @@ impl Zippy {
         Ok(magic == ZIPPY_MAGIC_UINT)
     }
 
-    pub fn decode(data: &[u8], _options: Option<ZippyOptions>) -> Result<Zippy, Error> {
+    pub fn decode(data: &[u8], options: Option<ZippyOptions>) -> Result<Zippy, Error> {
+        let options = options.unwrap_or_default();
+
         let mut data = Cursor::new(data);
 
         let magic = Self::read_u32(&mut data)?;
@@ -130,13 +148,18 @@ impl Zippy {
             name,
             description,
             features: HashSet::new(),
+            options,
             crc32: 0xffffffff,
             data: vec![],
         };
 
         instance.read_features(&mut data)?;
 
-        let buffer = Self::read_payload(&mut data)?;
+        let mut buffer = Self::read_buffer(&mut data)?;
+        if instance.has_feature(ZippyFeatures::EncryptedRc4) {
+            rc4_decrypt(instance.key()?, &mut buffer)
+        }
+
         let decoded = decode_rle(&decode_huffman(&buffer)?);
         instance.data = decoded;
 
@@ -145,7 +168,11 @@ impl Zippy {
 
     pub fn encode(&self) -> Result<Vec<u8>, Error> {
         let mut buffer = Cursor::new(vec![]);
-        let encoded = encode_huffman(&encode_rle(&self.data))?;
+        let mut encoded = encode_huffman(&encode_rle(&self.data))?;
+
+        if self.has_feature(ZippyFeatures::EncryptedRc4) {
+            rc4_encrypt(self.key()?, &mut encoded)
+        }
 
         Self::write_u32(&mut buffer, ZIPPY_MAGIC_UINT)?;
 
@@ -154,7 +181,7 @@ impl Zippy {
 
         self.write_features(&mut buffer)?;
 
-        Self::write_payload(&mut buffer, &encoded)?;
+        Self::write_buffer(&mut buffer, &encoded)?;
 
         Ok(buffer.into_inner())
     }
@@ -191,7 +218,7 @@ impl Zippy {
     }
 
     #[inline(always)]
-    fn read_payload(data: &mut Cursor<&[u8]>) -> Result<Vec<u8>, Error> {
+    fn read_buffer(data: &mut Cursor<&[u8]>) -> Result<Vec<u8>, Error> {
         let size = Self::read_u32(data)?;
         let mut payload = vec![0; size as usize];
         data.read_exact(&mut payload)?;
@@ -206,6 +233,7 @@ impl Zippy {
             let feature = ZippyFeatures::from(feature_str.as_str());
             match feature {
                 ZippyFeatures::Crc32 => self.read_crc32_feature(data)?,
+                ZippyFeatures::EncryptedRc4 => self.read_rc4_feature(data)?,
                 _ => self.read_empty_feature(data)?,
             };
             self.features.insert(feature);
@@ -215,7 +243,7 @@ impl Zippy {
 
     #[inline(always)]
     fn read_crc32_feature(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> {
-        let payload = Self::read_payload(data)?;
+        let payload = Self::read_buffer(data)?;
         if payload.len() != size_of::<u32>() {
             return Err(Error::InvalidData);
         }
@@ -224,9 +252,19 @@ impl Zippy {
         Ok(())
     }
 
+    #[inline(always)]
+    fn read_rc4_feature(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> {
+        let mut test_data = Self::read_buffer(data)?;
+        rc4_decrypt(self.key()?, &mut test_data);
+        if test_data != ZIPPY_CYPHER_TEST {
+            return Err(Error::InvalidKey);
+        }
+        Ok(())
+    }
+
     #[inline(always)]
     fn read_empty_feature(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> {
-        Self::read_payload(data)?;
+        Self::read_buffer(data)?;
         Ok(())
     }
 
@@ -244,7 +282,7 @@ impl Zippy {
     }
 
     #[inline(always)]
-    fn write_payload(data: &mut Cursor<Vec<u8>>, value: &[u8]) -> Result<(), Error> {
+    fn write_buffer(data: &mut Cursor<Vec<u8>>, value: &[u8]) -> Result<(), Error> {
         Self::write_u32(data, value.len() as u32)?;
         data.write_all(value)?;
         Ok(())
@@ -256,6 +294,7 @@ impl Zippy {
         for feature in &self.features {
             match feature {
                 ZippyFeatures::Crc32 => self.write_crc32_feature(data)?,
+                ZippyFeatures::EncryptedRc4 => self.write_rc4_feature(data)?,
                 _ => self.write_empty_feature(data, feature.into())?,
             }
         }
@@ -264,22 +303,44 @@ impl Zippy {
 
     #[inline(always)]
     fn write_crc32_feature(&self, data: &mut Cursor<Vec<u8>>) -> Result<(), Error> {
-        Self::write_string(data, "crc32")?;
+        Self::write_string(data, ZippyFeatures::Crc32.into())?;
         Self::write_u32(data, size_of::<u32>() as u32)?;
         Self::write_u32(data, self.crc32)?;
         Ok(())
     }
 
+    #[inline(always)]
+    fn write_rc4_feature(&self, data: &mut Cursor<Vec<u8>>) -> Result<(), Error> {
+        let mut test_data = ZIPPY_CYPHER_TEST.to_vec();
+        rc4_encrypt(self.key()?, &mut test_data);
+        Self::write_string(data, ZippyFeatures::EncryptedRc4.into())?;
+        Self::write_buffer(data, &test_data)?;
+        Ok(())
+    }
+
     #[inline(always)]
     fn write_empty_feature(&self, data: &mut Cursor<Vec<u8>>, name: &str) -> Result<(), Error> {
         Self::write_string(data, name)?;
         Self::write_u32(data, 0)?;
         Ok(())
     }
+
+    fn key(&self) -> Result<&[u8], Error> {
+        Ok(self
+            .options
+            .key
+            .as_ref()
+            .ok_or(Error::MissingOption(String::from("key")))?
+            .as_bytes())
+    }
 }
 
-pub fn encode_zippy(data: &[u8], options: Option<ZippyOptions>) -> Result<Vec<u8>, Error> {
-    Zippy::build(data, String::from(""), String::from(""), None, options)?.encode()
+pub fn encode_zippy(
+    data: &[u8],
+    features: Option<Vec<ZippyFeatures>>,
+    options: Option<ZippyOptions>,
+) -> Result<Vec<u8>, Error> {
+    Zippy::build(data, String::from(""), String::from(""), features, options)?.encode()
 }
 
 pub fn decode_zippy(data: &[u8], options: Option<ZippyOptions>) -> Result<Vec<u8>, Error> {
@@ -290,10 +351,10 @@ pub fn decode_zippy(data: &[u8], options: Option<ZippyOptions>) -> Result<Vec<u8
 mod tests {
     use boytacean_common::error::Error;
 
-    use super::{decode_zippy, Zippy, ZippyFeatures, ZippyOptions};
+    use super::{decode_zippy, encode_zippy, Zippy, ZippyFeatures, ZippyOptions};
 
     #[test]
-    fn test_build_and_encode() {
+    fn test_zippy_build_and_encode() {
         let data = vec![1, 2, 3, 4, 5];
         let name = String::from("Test");
         let description = String::from("Test description");
@@ -308,7 +369,7 @@ mod tests {
     }
 
     #[test]
-    fn test_decode_zippy() {
+    fn test_zippy_decode() {
         let data = vec![1, 2, 3, 4, 5];
         let name = String::from("Test");
         let description = String::from("Test description");
@@ -321,7 +382,7 @@ mod tests {
     }
 
     #[test]
-    fn test_crc32_zippy() {
+    fn test_zippy_crc32() {
         let data = vec![1, 2, 3, 4, 5];
         let name = String::from("Test");
         let description = String::from("Test description");
@@ -336,7 +397,7 @@ mod tests {
     }
 
     #[test]
-    fn test_no_crc32_zippy() {
+    fn test_zippy_no_crc32() {
         let data = vec![1, 2, 3, 4, 5];
         let name = String::from("Test");
         let description = String::from("Test description");
@@ -346,7 +407,7 @@ mod tests {
             name.clone(),
             description.clone(),
             None,
-            Some(ZippyOptions::new(false)),
+            Some(ZippyOptions::new(false, None)),
         )
         .unwrap();
         let encoded = zippy.encode().unwrap();
@@ -358,14 +419,14 @@ mod tests {
     }
 
     #[test]
-    fn test_decode_invalid() {
+    fn test_zippy_decode_invalid() {
         let decoded_data = decode_zippy(b"invalid", None);
         assert!(decoded_data.is_err());
         assert_eq!(decoded_data.unwrap_err(), Error::InvalidData);
     }
 
     #[test]
-    fn test_dummy_feature() {
+    fn test_zippy_dummy_feature() {
         let data = vec![1, 2, 3, 4, 5];
         let name = String::from("Test");
         let description = String::from("Test description");
@@ -375,7 +436,7 @@ mod tests {
             name.clone(),
             description.clone(),
             Some(vec![ZippyFeatures::Other]),
-            Some(ZippyOptions::new(false)),
+            Some(ZippyOptions::new(false, None)),
         )
         .unwrap();
         let encoded = zippy.encode().unwrap();
@@ -386,4 +447,52 @@ mod tests {
         assert!(!zippy.check_crc32());
         assert_eq!(zippy.crc32(), 0xffffffff);
     }
+
+    #[test]
+    fn test_zippy_encrypted() {
+        let encoded = encode_zippy(
+            b"test",
+            Some(vec![ZippyFeatures::EncryptedRc4]),
+            Some(ZippyOptions::new(false, Some(String::from("key")))),
+        )
+        .unwrap();
+        let decoded = decode_zippy(
+            &encoded,
+            Some(ZippyOptions::new(false, Some(String::from("key")))),
+        )
+        .unwrap();
+        assert_eq!(decoded, b"test");
+    }
+
+    #[test]
+    fn test_zippy_wrong_key() {
+        let encoded = encode_zippy(
+            b"test",
+            Some(vec![ZippyFeatures::EncryptedRc4]),
+            Some(ZippyOptions::new(false, Some(String::from("key")))),
+        )
+        .unwrap();
+        let decoded = decode_zippy(
+            &encoded,
+            Some(ZippyOptions::new(false, Some(String::from("wrong_key")))),
+        );
+        assert!(decoded.is_err());
+        assert_eq!(decoded.unwrap_err(), Error::InvalidKey);
+    }
+
+    #[test]
+    fn test_zippy_no_key() {
+        let encoded = encode_zippy(
+            b"test",
+            Some(vec![ZippyFeatures::EncryptedRc4]),
+            Some(ZippyOptions::new(false, Some(String::from("key")))),
+        )
+        .unwrap();
+        let decoded = decode_zippy(&encoded, Some(ZippyOptions::new(false, None)));
+        assert!(decoded.is_err());
+        assert_eq!(
+            decoded.unwrap_err(),
+            Error::MissingOption(String::from("key"))
+        );
+    }
 }
diff --git a/src/state.rs b/src/state.rs
index 044f0e455d749c1f6c3c4830feac46b990e4682f..89aa7d9a2970658b9ac44001fcd2c9bf06c535be 100644
--- a/src/state.rs
+++ b/src/state.rs
@@ -225,7 +225,7 @@ impl Serialize for BoscState {
         let mut cursor = Cursor::new(vec![]);
         self.bos.write(&mut cursor)?;
 
-        let bos_compressed = encode_zippy(&cursor.into_inner(), None)?;
+        let bos_compressed = encode_zippy(&cursor.into_inner(), None, None)?;
         buffer.write_all(&bos_compressed)?;
 
         Ok(())
@@ -2050,7 +2050,7 @@ mod tests {
             .unwrap();
         gb.step_to(0x0100);
         let data = StateManager::save(&mut gb, Some(SaveStateFormat::Bess), None).unwrap();
-        let encoded = encode_zippy(&data, None).unwrap();
+        let encoded = encode_zippy(&data, None, None).unwrap();
         let decoded = decode_zippy(&encoded, None).unwrap();
         assert_eq!(data, decoded);
         assert_eq!(encoded.len(), 847);