1use std::io::Cursor;
7use std::io::Read;
8use std::ops::Deref;
9
10use binrw::BinRead;
11use binrw::BinResult;
12use binrw::BinWrite;
13use binrw::binrw;
14use binrw::helpers::until_eof;
15use bitflags::bitflags;
16
17use lzma_rs::lzma_compress_with_options;
18use lzma_rs::lzma_decompress_with_options;
19
20use crate::constants::Mode;
21use crate::constants::Rule;
22use crate::data::Deck;
23use crate::data::Response;
24use crate::message::HostInfo;
25use crate::utils::string::FixedLengthString;
26
27const SIZE_REPLAY_SEED: usize = 8;
28
29#[repr(u32)]
30pub enum ReplayVersion {
31 V1 = 0x31707279,
33 V2 = 0x32707279
35}
36
37bitflags! {
38 #[derive(BinRead, BinWrite, Clone, Debug, PartialEq, Eq)]
39 #[br(map=|x: u32| Self::from_bits_retain(x))]
40 #[bw(map=|x: &Self| x.bits())]
41 pub struct ReplayHeaderFlags: u32 {
42 const Compressed = 1;
43 const Tag = 2;
44 const Decode = 4;
45 const SingleMode = 8;
46 const Uniform = 16;
47 }
48}
49
50bitflags! {
51 #[derive(BinRead, BinWrite, Clone, Debug)]
52 #[br(map=|x: u16| Self::from_bits_retain(x))]
53 #[bw(map=|x: &Self| x.bits())]
54 pub struct DuelOptions: u16 {
55 const TestMode = 0x1;
56 const AttackFirstTurn = 0x2;
57 const OldReplay = 0x4;
58 const ObsoleteRuling = 0x8;
59 const PseudoShuffle = 0x10;
60 const TagMode = 0x20;
61 const SimpleAI = 0x40;
62 const ReturnDeckTop = 0x80;
63 const RevealDeckSequence = 0x100;
64 }
65}
66
67bitflags! {
68 #[derive(BinRead, BinWrite, Clone, Debug)]
69 #[br(map=|x: u32| Self::from_bits_retain(x))]
70 #[bw(map=|x: &Self| x.bits())]
71 pub struct ReplayMode: u32 {
72 const SaveInServer = 1;
73 const WatcherNoSend = 2;
74 const IncludeChat = 4;
75 }
76}
77
78#[derive(BinRead, BinWrite, Clone, Debug)]
79pub struct ReplayHeader {
80 pub id: u32,
81 pub version: u32,
82 pub flag: ReplayHeaderFlags,
83 pub seed: u32,
84 pub data_size: u32,
85 pub start_time: u32,
86 pub props: [u8; 8],
87 #[br(if(id == ReplayVersion::V2 as u32))]
88 #[bw(if(*id == ReplayVersion::V2 as u32))]
89 pub seed_sequence: [u32; SIZE_REPLAY_SEED],
90 #[br(if(id == ReplayVersion::V2 as u32))]
91 #[bw(if(*id == ReplayVersion::V2 as u32))]
92 pub header_version: u32,
93 #[br(if(id == ReplayVersion::V2 as u32))]
94 #[bw(if(*id == ReplayVersion::V2 as u32))]
95 pub reserved: [u32; 3],
96}
97
98impl ReplayHeader {
99 pub fn is_compressed(&self) -> bool { self.flag.contains(ReplayHeaderFlags::Compressed) }
100 pub fn is_tag(&self) -> bool { self.flag.contains(ReplayHeaderFlags::Tag) }
101 pub fn is_decoded(&self) -> bool { self.flag.contains(ReplayHeaderFlags::Decode) }
102 pub fn is_single_mode(&self)-> bool { self.flag.contains(ReplayHeaderFlags::SingleMode) }
103 pub fn is_uniform(&self) -> bool { self.flag.contains(ReplayHeaderFlags::Uniform) }
104}
105
106#[binrw]
110#[derive(PartialEq, Eq, Debug, Clone, Default)]
111pub struct ReplayDeck {
112 #[bw(calc = main.len() as u32)]
113 main_size: u32,
114 #[br(count = main_size)]
115 pub main: Vec<u32>,
116 #[bw(calc = extra.len() as u32)]
117 extra_size: u32,
118 #[br(count = extra_size)]
119 pub extra: Vec<u32>,
120}
121
122impl From<Deck> for ReplayDeck {
123 fn from(value: Deck) -> Self {
124 let mut main = value.main;
125 let mut extra = value.extra;
126 main.reverse();
127 extra.reverse();
128 Self { main, extra }
129 }
130}
131
132impl From<ReplayDeck> for Deck {
133 fn from(value: ReplayDeck) -> Self {
134 let mut main = value.main.clone();
135 let mut extra = value.extra.clone();
136 main.reverse();
137 extra.reverse();
138 Self { main, side: vec![], extra }
139 }
140}
141
142#[binrw]
143#[derive(Clone, Debug)]
144#[br(import(header: &ReplayHeader))]
145#[bw(import(header: &ReplayHeader))]
146pub struct ReplayBody {
147 pub host_name: FixedLengthString<20>,
148 #[br(if(header.is_tag()))]
149 #[bw(if(header.is_tag()))]
150 pub tag_host_name: Option<FixedLengthString<20>>,
151 #[br(if(header.is_tag()))]
152 #[bw(if(header.is_tag()))]
153 pub tag_client_name: Option<FixedLengthString<20>>,
154 pub client_name: FixedLengthString<20>,
155 pub start_lp: u32,
156 pub start_hand: u32,
157 pub draw_count: u32,
158 pub duel_options: DuelOptions,
160 pub duel_rule: u16,
161 pub host_deck: ReplayDeck,
162 #[br(if(header.is_tag()))]
163 #[bw(if(header.is_tag()))]
164 pub tag_host_deck: Option<ReplayDeck>,
165 pub client_deck: ReplayDeck,
166 #[br(if(header.is_tag()))]
167 #[bw(if(header.is_tag()))]
168 pub tag_client_deck: Option<ReplayDeck>,
169 #[br(parse_with=until_eof)]
170 pub datas: Vec<ReplayData>
171}
172
173#[binrw]
174#[derive(Clone, Debug)]
175pub struct ReplayData {
176 #[bw(calc(data.len() as u8))]
177 size: u8,
178 #[br(count = size, map = |bytes: Vec<u8>| Response::Unknown(bytes))]
179 pub data: Response
180}
181
182impl From<Response> for ReplayData {
183 fn from(value: Response) -> Self {
184 ReplayData { data: value }
185 }
186}
187
188#[derive(BinRead, BinWrite, Debug, Clone)]
189pub struct Replay {
190 pub header: ReplayHeader,
191 #[br(parse_with = replay_parser, args(&header))]
192 #[bw(write_with = replay_writer, args(&header))]
193 pub body: ReplayBody
194}
195
196impl Replay {
197 pub fn fill_data_size(&mut self) {
198 let host = &self.body.host_deck;
199 let client = &self.body.client_deck;
200 let mut size = 40 + 40
201 + 4 + 4 + 4 + 2 + 2
202 + 4 + host.main.len() as u32 * 4 + 4 + host.extra.len() as u32 * 4
203 + 4 + client.main.len() as u32 * 4 + 4 + client.extra.len() as u32 * 4
204 + self.body.datas.iter().map(|d| 1 + d.data.len() as u32).sum::<u32>();
205
206 if self.header.is_tag() {
207 size += 40 + 40;
208 if let Some(ref tag_host) = self.body.tag_host_deck {
209 size += 4 + tag_host.main.len() as u32 * 4 + 4 + tag_host.extra.len() as u32 * 4;
210 }
211 if let Some(ref tag_client) = self.body.tag_client_deck {
212 size += 4 + tag_client.main.len() as u32 * 4 + 4 + tag_client.extra.len() as u32 * 4;
213 }
214 }
215 self.header.data_size = size;
216 }
217
218 pub fn duel_rule(&self) -> crate::constants::MasterRule {
219 if self.duel_options.contains(DuelOptions::ObsoleteRuling) { crate::constants::MasterRule::MasterRule1 }
220 else { crate::constants::MasterRule::try_from(self.duel_rule as u8).unwrap_or(crate::constants::MasterRule::MasterRule1) }
221 }
222
223 pub fn mode(&self) -> Mode {
224 if self.duel_options.contains(DuelOptions::TagMode) { Mode::Tag }
225 else { Mode::Single }
226 }
227 pub fn no_shuffle_deck(&self) -> bool { self.duel_options.contains(DuelOptions::PseudoShuffle) }
228 pub fn is_tag(&self) -> bool { self.duel_options.contains(DuelOptions::TagMode) }
229
230 pub fn host_info(&self) -> HostInfo {
231 HostInfo {
232 lflist: 999,
233 rule: Rule::OCG,
234 mode: self.mode(),
235 duel_rule: self.duel_rule(),
236 no_check_deck: true,
237 no_shuffle_deck: self.no_shuffle_deck(),
238 start_lp: self.start_lp,
239 start_hand: self.start_hand as u8,
240 draw_count: self.draw_count as u8,
241 time_limit: 0
242 }
243 }
244}
245
246impl Deref for Replay {
247 type Target = ReplayBody;
248
249 fn deref(&self) -> &Self::Target {
250 &self.body
251 }
252}
253
254#[derive(BinRead)]
255struct ReadHelper {
256 #[br(parse_with = until_eof)]
257 content: Vec<u8>
258}
259
260#[binrw::parser(reader, endian)]
269fn replay_parser(header: &ReplayHeader) -> BinResult<ReplayBody> {
270 let leading_props = Cursor::new(&header.props[0..5]);
271 let helper = ReadHelper::read_options(reader, endian, ())?;
272 let compressed_data = helper.content;
273 let decompressed_data = if header.is_compressed() {
274 let mut compressed_data = leading_props.chain(Cursor::new(compressed_data));
275 let mut decompressed_data = Vec::new();
276 lzma_decompress_with_options(&mut compressed_data, &mut decompressed_data, &lzma_rs::decompress::Options {
277 unpacked_size: lzma_rs::decompress::UnpackedSize::UseProvided(Some(header.data_size as u64)),
278 memlimit: None,
279 allow_incomplete: false
280 }).map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
281 decompressed_data
282 }
283 else { compressed_data };
284 <_>::read_options(&mut Cursor::new(decompressed_data), endian, (header,))
285}
286
287#[binrw::writer(writer, endian)]
289fn replay_writer(body: &ReplayBody, header: &ReplayHeader) -> BinResult<()> {
290 let mut decompressed_data = Cursor::new(Vec::new());
291 body.write_options(&mut decompressed_data, endian, (header,))?;
292 let compressed_data = if header.is_compressed() {
293 let mut compressed_data = Cursor::new(Vec::new());
294 decompressed_data.set_position(0);
295 lzma_compress_with_options(&mut decompressed_data, &mut compressed_data, &lzma_rs::compress::Options {
296 unpacked_size: lzma_rs::compress::UnpackedSize::SkipWritingToHeader
297 })?;
298 let mut data = compressed_data.into_inner();
299 data.drain(..5); data
301 } else { decompressed_data.into_inner() };
302 compressed_data.write_options(writer, endian, ())
303}
304
305mod test {
306 #![allow(unused_imports)]
307
308 use std::io::Cursor;
309 use binrw::BinRead;
310 use crate::data::Replay;
311
312 #[test]
313 #[ignore]
314 fn test_deserialize_replay() {
315 let arr = std::fs::read("/Users/iami/Downloads/极羽光_vs_爱尔琳妮_20260531225205_G1.yrp").unwrap();
316 let mut reader = Cursor::new(arr);
317 let replay = Replay::read_le(&mut reader);
318 println!("{:?}", replay);
319 }
320
321 #[test]
322 fn test_replay_roundtrip() {
323 use binrw::BinWrite;
324 use crate::data::ReplayHeader;
325 use crate::data::ReplayHeaderFlags;
326 use crate::data::ReplayVersion;
327 use crate::data::ReplayBody;
328 use crate::data::ReplayDeck;
329 use crate::data::ReplayData;
330 use crate::data::Response;
331 use crate::data::DuelOptions;
332 use crate::utils::string::FixedLengthString;
333
334 let mut original = Replay {
335 header: ReplayHeader {
336 id: ReplayVersion::V2 as u32,
337 version: 0x1362,
338 flag: ReplayHeaderFlags::Uniform | ReplayHeaderFlags::Compressed,
339 seed: 0,
340 data_size: 0,
341 start_time: 1234567890,
342 props: [93, 0, 0, 128, 0, 0, 0, 0],
343 seed_sequence: [1, 2, 3, 4, 5, 6, 7, 8],
344 header_version: 1,
345 reserved: [0; 3],
346 },
347 body: ReplayBody {
348 host_name: FixedLengthString::new("Host".to_string()),
349 client_name: FixedLengthString::new("Client".to_string()),
350 tag_host_name: None,
351 tag_client_name: None,
352 start_lp: 8000,
353 start_hand: 5,
354 draw_count: 1,
355 duel_options: DuelOptions::empty(),
356 duel_rule: 5,
357 host_deck: ReplayDeck::default(),
358 client_deck: ReplayDeck::default(),
359 tag_host_deck: None,
360 tag_client_deck: None,
361 datas: vec![ReplayData { data: Response::Unknown(vec![1, 2, 3]) }],
362 },
363 };
364 original.fill_data_size();
365
366 let mut buf = Cursor::new(Vec::new());
367 original.write_le(&mut buf).unwrap();
368 let written = buf.into_inner();
369
370 let decoded = Replay::read_le(&mut Cursor::new(written)).unwrap();
371 assert_eq!(decoded.header.id, original.header.id);
372 assert_eq!(decoded.header.version, original.header.version);
373 assert_eq!(decoded.header.flag, original.header.flag);
374 assert_eq!(decoded.header.data_size, original.header.data_size);
375 assert_eq!(decoded.header.seed_sequence, original.header.seed_sequence);
376 assert_eq!(decoded.body.host_name.to_string(), original.body.host_name.to_string());
377 assert_eq!(decoded.body.client_name.to_string(), original.body.client_name.to_string());
378 assert_eq!(decoded.body.start_lp, original.body.start_lp);
379 assert_eq!(decoded.body.duel_rule, original.body.duel_rule);
380 assert_eq!(decoded.body.datas.len(), original.body.datas.len());
381 }
382}