// SPDX-FileCopyrightText: 2023 Joshua Goins // SPDX-License-Identifier: GPL-3.0-or-later use std::io::{Cursor, Seek, SeekFrom}; use binrw::BinRead; use binrw::binrw; use crate::gamedata::MemoryBuffer; #[binrw] #[allow(dead_code)] #[brw(little)] pub struct ChatLogHeader { content_size: u32, file_size: u32, #[br(count = file_size - content_size)] offset_entries: Vec, } #[binrw] #[brw(repr = u8)] #[derive(Debug)] enum EventFilter { SystemMessages = 3, Unknown = 20, ProgressionMessage = 64, NPCBattle = 41, Unknown2 = 57, Unknown7 = 29, Unknown3 = 59, EnemyBattle = 170, } #[binrw] #[derive(Debug)] #[brw(repr = u8)] enum EventChannel { System = 0, ServerAnnouncement = 3, Unknown1 = 50, Unknown7 = 29, Others = 32, Unknown5 = 41, NPCEnemy = 51, NPCFriendly = 59, Unknown4 = 64, Unknown6 = 170, } #[binrw] #[derive(Debug)] #[allow(dead_code)] #[brw(little)] pub struct ChatLogEntry { timestamp: u32, filter: EventFilter, channel: EventChannel, #[br(temp)] #[bw(calc = 1)] garbage: u32, #[brw(ignore)] message: String, } #[derive(Debug)] #[allow(dead_code)] pub struct ChatLog { entries: Vec, } impl ChatLog { pub fn from_existing(buffer: &MemoryBuffer) -> Option { let mut cursor = Cursor::new(buffer); let header = ChatLogHeader::read(&mut cursor).expect("Cannot parse header."); let content_offset = (8 + header.file_size * 4) as u64; // beginning of content offset cursor.seek(SeekFrom::Start(content_offset)).ok()?; let mut entries = vec![]; for offset in header.offset_entries { let new_last_offset = content_offset + offset as u64; let mut entry = ChatLogEntry::read(&mut cursor).expect("Unable to parse log message."); // TODO: handle the coloring properly, in some way entry.message = String::from_utf8_lossy( &buffer[cursor.position() as usize..new_last_offset as usize], ) .to_string(); cursor.seek(SeekFrom::Start(new_last_offset)).ok()?; entries.push(entry); } Some(ChatLog { entries }) } }