1use std::collections::BTreeMap;
35use std::fmt;
36use std::io;
37use std::path::PathBuf;
38
39use encoding_rs::{Encoding, WINDOWS_1250, WINDOWS_1251, WINDOWS_1252};
40
41mod content_files;
42mod events;
43mod fallback_keys;
44mod importer;
45#[cfg(feature = "lua")]
46pub mod lua;
47mod openmw_cfg;
48mod parser;
49mod plugin;
50#[cfg(test)]
51mod test_support;
52mod warnings;
53
54pub use events::ImportEvent;
55pub use importer::{ImportOptions, ImportReport, ImportResult, IniImporter};
56pub use openmw_cfg::{
57 PreservedCfgUpdate, apply_preserved_cfg_update, load_cfg_document, save_cfg_output_to_path,
58 save_preserved_cfg_document_to_path, save_resolved_cfg_to_path,
59 save_resolved_configuration_to_path, serialize_cfg_output, serialize_preserved_cfg_document,
60 serialize_resolved_cfg, serialize_resolved_configuration,
61};
62pub use parser::{
63 ParsedIni, parse_cfg_str, parse_ini_bytes, parse_ini_bytes_with_warnings, parse_ini_str,
64 parse_ini_str_with_warnings, serialize_cfg,
65};
66pub use plugin::{PluginHeader, read_plugin_header};
67pub use warnings::ImportWarning;
68
69pub type MultiMap = BTreeMap<String, Vec<String>>;
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum Game {
73 Morrowind,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum PluginFormat {
78 Tes3,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum TextEncoding {
83 Win1250,
84 Win1251,
85 Win1252,
86}
87
88impl TextEncoding {
89 pub fn parse(value: &str) -> Result<Self, ImportError> {
94 match value.to_ascii_lowercase().as_str() {
95 "win1250" | "windows-1250" => Ok(Self::Win1250),
96 "win1251" | "windows-1251" => Ok(Self::Win1251),
97 "win1252" | "windows-1252" => Ok(Self::Win1252),
98 _ => Err(ImportError::UnsupportedEncoding(value.to_owned())),
99 }
100 }
101
102 pub(crate) fn as_label(self) -> &'static str {
103 match self {
104 Self::Win1250 => "win1250",
105 Self::Win1251 => "win1251",
106 Self::Win1252 => "win1252",
107 }
108 }
109
110 pub(crate) fn encoding_rs(self) -> &'static Encoding {
111 match self {
112 Self::Win1250 => WINDOWS_1250,
113 Self::Win1251 => WINDOWS_1251,
114 Self::Win1252 => WINDOWS_1252,
115 }
116 }
117}
118
119#[derive(Debug)]
120#[non_exhaustive]
121pub enum ImportError {
122 Io {
123 path: PathBuf,
124 source: io::Error,
125 },
126 UnsupportedEncoding(String),
127 InvalidPluginHeader {
128 path: PathBuf,
129 message: String,
130 },
131 MissingContentFiles {
132 files: Vec<String>,
133 searched_paths: Vec<PathBuf>,
134 },
135 MissingArchives {
136 files: Vec<String>,
137 searched_paths: Vec<PathBuf>,
138 },
139 InvalidContentFileName(String),
140 InvalidArchiveName(String),
141 OpenMwConfig(String),
142}
143
144impl fmt::Display for ImportError {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 match self {
147 Self::Io { path, source } => write!(f, "{}: {}", path.display(), source),
148 Self::UnsupportedEncoding(value) => write!(f, "unsupported encoding: {value}"),
149 Self::InvalidPluginHeader { path, message } => {
150 write!(f, "invalid plugin header in {}: {message}", path.display())
151 }
152 Self::MissingContentFiles {
153 files,
154 searched_paths,
155 } => {
156 write!(f, "content files not found: {}", files.join(", "))?;
157 if !searched_paths.is_empty() {
158 write!(
159 f,
160 "; searched: {}",
161 searched_paths
162 .iter()
163 .map(|path| path.display().to_string())
164 .collect::<Vec<_>>()
165 .join(", ")
166 )?;
167 }
168 write!(f, "; pass --data or add data=... to the cfg")
169 }
170 Self::MissingArchives {
171 files,
172 searched_paths,
173 } => {
174 write!(f, "fallback archives not found: {}", files.join(", "))?;
175 if !searched_paths.is_empty() {
176 write!(
177 f,
178 "; searched: {}",
179 searched_paths
180 .iter()
181 .map(|path| path.display().to_string())
182 .collect::<Vec<_>>()
183 .join(", ")
184 )?;
185 }
186 write!(f, "; pass --data or add data=... to the cfg")
187 }
188 Self::InvalidContentFileName(file) => write!(
189 f,
190 "invalid content file name: {file}; content entries must be plugin filenames, not paths"
191 ),
192 Self::InvalidArchiveName(file) => write!(
193 f,
194 "invalid fallback archive name: {file}; archive entries must be BSA filenames, not paths"
195 ),
196 Self::OpenMwConfig(message) => write!(f, "OpenMW config error: {message}"),
197 }
198 }
199}
200
201impl std::error::Error for ImportError {}
202
203#[must_use]
204pub fn known_fallback_keys() -> &'static [&'static str] {
205 fallback_keys::MORROWIND_FALLBACK_KEYS
206}
207
208#[cfg(test)]
209mod lib_tests;