physis/savedata/
log.rs

1// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use 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)]
90/// Represents an entry in the chat log
91pub struct ChatLogEntry {
92    timestamp: u32,
93    /// The event filter
94    pub filter: EventFilter,
95    /// The event channel
96    pub channel: EventChannel,
97
98    #[br(temp)]
99    #[bw(calc = 1)]
100    _garbage: u32,
101
102    /// The message
103    #[brw(ignore)]
104    pub message: String,
105}
106
107#[derive(Debug)]
108#[allow(dead_code)]
109/// Chat log, which contains previously recorded messages from other players
110pub struct ChatLog {
111    pub entries: Vec<ChatLogEntry>,
112}
113
114impl ChatLog {
115    /// Reads an existing LOG file
116    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        // Dumb check for obviously wrong values
121        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        // beginning of content offset
128        //cursor.seek(SeekFrom::Start(content_offset)).ok()?;
129
130        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            // TODO: handle the coloring properly, in some way
146            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        // Feeding it invalid data should not panic
171        ChatLog::from_existing(&read(d).unwrap());
172    }
173}