1use std::io::{Cursor, Seek, SeekFrom};
5
6use crate::ByteSpan;
7use binrw::BinRead;
8use binrw::binrw;
9
10#[binrw]
11#[allow(dead_code)]
12#[brw(little)]
13struct ChatLogHeader {
14 content_size: u32,
15 file_size: u32,
16
17 #[br(count = file_size.saturating_sub(content_size))]
18 offset_entries: Vec<u32>,
19}
20
21#[binrw]
22#[brw(repr = u8)]
23#[derive(Debug)]
24pub enum EventFilter {
25 SystemMessages = 3,
26 Unknown = 20,
27 ProgressionMessage = 64,
28 NPCBattle = 41,
29 Unknown2 = 57,
30 Unknown7 = 29,
31 Unknown3 = 59,
32 EnemyBattle = 170,
33 Unknown4 = 581,
34 Unknown8 = 43,
35 Unknown9 = 60,
36 Unknown10 = 61,
37 Unknown11 = 62,
38 Unknown12 = 58,
39 Unknown13 = 169,
40 Unknown14 = 175,
41 Unknown15 = 42,
42 Unknown16 = 171,
43 Unknown17 = 177,
44 Unknown18 = 174,
45 Unknown19 = 47,
46 Unknown20 = 176,
47 Unknown21 = 44,
48 Unknown22 = 173,
49 Unknown23 = 46,
50 Unknown24 = 10,
51 Unknown25 = 185,
52 Unknown26 = 190,
53 Unknown27 = 11,
54 Unknown28 = 70,
55 Unknown29 = 105,
56}
57
58#[binrw]
59#[derive(Debug)]
60#[brw(repr = u8)]
61pub enum EventChannel {
62 System = 0,
63 Unknown8 = 2,
64 ServerAnnouncement = 3,
65 Unknown9 = 8,
66 Unknown1 = 50,
67 Unknown7 = 29,
68 Others = 32,
69 Unknown5 = 41,
70 NPCEnemy = 51,
71 NPCFriendly = 59,
72 Unknown4 = 64,
73 Unknown6 = 170,
74 Unknown10 = 10,
75 Unknown11 = 66,
76 Unknown12 = 44,
77 Unknown13 = 40,
78 Unknown14 = 42,
79 Unknown15 = 11,
80 Unknown16 = 67,
81 Unknown17 = 68,
82 Unknown18 = 34,
83 Unknown19 = 110,
84}
85
86#[binrw]
87#[derive(Debug)]
88#[allow(dead_code)]
89#[brw(little)]
90pub struct ChatLogEntry {
92 timestamp: u32,
93 pub filter: EventFilter,
95 pub channel: EventChannel,
97
98 #[br(temp)]
99 #[bw(calc = 1)]
100 _garbage: u32,
101
102 #[brw(ignore)]
104 pub message: String,
105}
106
107#[derive(Debug)]
108#[allow(dead_code)]
109pub struct ChatLog {
111 pub entries: Vec<ChatLogEntry>,
112}
113
114impl ChatLog {
115 pub fn from_existing(buffer: ByteSpan) -> Option<ChatLog> {
117 let mut cursor = Cursor::new(buffer);
118
119 let header = ChatLogHeader::read(&mut cursor).expect("Cannot parse header.");
120 if header.content_size as usize > buffer.len() || header.file_size as usize > buffer.len() {
122 return None;
123 }
124
125 let content_offset = (8 + header.file_size * 4) as u64;
126
127 let mut entries = vec![];
131
132 for (i, offset) in header.offset_entries.iter().enumerate() {
133 let new_last_offset = content_offset + *offset as u64;
134
135 cursor.seek(SeekFrom::Start(new_last_offset)).ok()?;
136
137 let mut entry = ChatLogEntry::read(&mut cursor).expect("Unable to parse log message.");
138
139 let next_offset = if i + 1 == header.offset_entries.len() {
140 buffer.len()
141 } else {
142 (content_offset + header.offset_entries[i + 1] as u64) as usize
143 };
144
145 entry.message =
147 String::from_utf8_lossy(&buffer[cursor.position() as usize..next_offset])
148 .to_string();
149
150 entries.push(entry);
151 }
152
153 Some(ChatLog { entries })
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use std::fs::read;
160 use std::path::PathBuf;
161
162 use super::*;
163
164 #[test]
165 fn test_invalid() {
166 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
167 d.push("resources/tests");
168 d.push("random");
169
170 ChatLog::from_existing(&read(d).unwrap());
172 }
173}