use std::{borrow::Cow, fmt::Display, str::FromStr};
use crate::{
defs::ResTableRef,
stream::{
NewResultCtx, Readable, ReadableNoOptions, StreamError, StreamResult, Writeable,
WriteableNoOptions,
},
string_pool::{ResStringPoolRef, StringPoolHandler},
xmltree::NodeDifference,
};
#[derive(Debug, PartialEq, Clone, Copy, Default)]
pub struct ResValue {
pub data: ResValueType,
}
impl Readable for ResValue {
type Args = ();
fn read<R: std::io::Read + std::io::Seek>(
reader: &mut R,
_args: Self::Args,
) -> StreamResult<Self> {
let size = u16::read_no_opts(reader).add_context(|| "read size for ResValue")?;
if size != 8 {
return Err(StreamError::new_string_context(
format!("invalid size {size}, expected 8"),
reader.stream_position()?,
"validate size for ResValue",
));
}
let res0 = u8::read_no_opts(reader).add_context(|| "read res0 for ResValue")?;
if res0 != 0 {
return Err(StreamError::new_string_context(
format!("invalid res0 {res0}, expected 0"),
reader.stream_position()?,
"validate res0 for ResValue",
));
}
Ok(Self {
data: ResValueType::read_no_opts(reader).add_context(|| "read data for ResValue")?,
})
}
}
impl Writeable for ResValue {
type Args = ();
fn write<W: std::io::Write + std::io::Seek>(
self,
writer: &mut W,
_args: Self::Args,
) -> StreamResult<()> {
let size: u16 = 8;
size.write_no_opts(writer)
.add_context(|| "write size for ResValue")?;
let res0: u8 = 0;
res0.write_no_opts(writer)
.add_context(|| "write res0 for ResValue")?;
self.data
.write_no_opts(writer)
.add_context(|| "write data for ResValue")
}
}
impl ResValue {
pub fn new(data: ResValueType) -> Self {
Self { data }
}
pub fn new_str_ref(string: ResStringPoolRef) -> Self {
Self {
data: ResValueType::String(string),
}
}
pub fn new_str(string: Cow<'_, str>, string_pool: &mut StringPoolHandler) -> Self {
Self::new_str_ref(string_pool.allocate(string))
}
pub fn write_string(&mut self, string: Cow<'_, str>, string_pool: &mut StringPoolHandler) {
let reference = string_pool.allocate(string);
self.data = ResValueType::String(reference);
}
pub fn new_bool(value: bool) -> Self {
Self::new(ResValueType::IntBoolean(value.into()))
}
pub fn equivalent(
&self,
other: ResValue,
string_pool: &StringPoolHandler,
other_string_pool: &StringPoolHandler,
) -> crate::xmltree::NodeDifference {
match self.data {
ResValueType::IntHex(val) => match other.data {
ResValueType::IntDec(val2) => NodeDifference::from_2_res_value(val, val2),
ResValueType::IntHex(val2) => NodeDifference::from_2_res_value(val, val2),
_ => NodeDifference::from_2_res_value(self.data, other.data),
},
ResValueType::IntDec(val) => match other.data {
ResValueType::IntDec(val2) => NodeDifference::from_2_res_value(val, val2),
ResValueType::IntHex(val2) => NodeDifference::from_2_res_value(val, val2),
_ => NodeDifference::from_2_res_value(self.data, other.data),
},
ResValueType::String(val) => {
if let ResValueType::String(others) = other.data {
if val.resolve(string_pool) != others.resolve(other_string_pool) {
return NodeDifference::ResValueString(
val.resolve(string_pool).map(|v| v.to_string()),
others.resolve(other_string_pool).map(|v| v.to_string()),
);
} else {
return NodeDifference::None;
}
} else {
return NodeDifference::WrongResValueType(self.data, other.data);
}
}
_ => NodeDifference::from_2_res_value(self.data, other.data),
}
}
}
impl NodeDifference {
pub fn from_2_res_value<T: PartialEq + std::fmt::Debug>(one: T, other: T) -> Self {
if one == other {
Self::None
} else {
Self::ResValue(format!("{:?}", one), format!("{:?}", other))
}
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum ResTypeNullType {
Undefined,
Empty,
}
impl Readable for ResTypeNullType {
type Args = ();
fn read<R: std::io::Read + std::io::Seek>(
reader: &mut R,
_args: Self::Args,
) -> StreamResult<Self> {
let res_type =
u32::read_no_opts(reader).add_context(|| "read value id for ResTypeNullType")?;
Ok(match res_type {
0 => Self::Undefined,
1 => Self::Empty,
v => {
return Err(StreamError::new_string_context(
format!("invalid null value: {v}, expected 0 or 1"),
reader.stream_position()?,
"read ResTypeNullType",
));
}
})
}
}
impl Writeable for ResTypeNullType {
type Args = ();
fn write<W: std::io::Write + std::io::Seek>(
self,
writer: &mut W,
_args: Self::Args,
) -> StreamResult<()> {
let res_type: u32 = match self {
Self::Undefined => 0,
Self::Empty => 1,
};
res_type
.write_no_opts(writer)
.add_context(|| "write ResTypeNullType value")
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum ResTypeBoolType {
False,
True,
}
impl From<ResTypeBoolType> for bool {
fn from(value: ResTypeBoolType) -> Self {
match value {
ResTypeBoolType::False => false,
ResTypeBoolType::True => true,
}
}
}
impl Readable for ResTypeBoolType {
type Args = ();
fn read<R: std::io::Read + std::io::Seek>(
reader: &mut R,
_args: Self::Args,
) -> StreamResult<Self> {
let bool_type = u32::read_no_opts(reader)?;
Ok(match bool_type {
0 => Self::False,
_ => Self::True,
})
}
}
impl Writeable for ResTypeBoolType {
type Args = ();
fn write<W: std::io::Write + std::io::Seek>(
self,
writer: &mut W,
_args: Self::Args,
) -> StreamResult<()> {
let bool_type: u32 = match self {
Self::False => 0,
Self::True => 0xffffffff,
};
bool_type
.write_no_opts(writer)
.add_context(|| "write ResTypeBoolType")
}
}
impl Display for ResTypeBoolType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
ResTypeBoolType::True => "true",
ResTypeBoolType::False => "false",
};
write!(f, "{str}")
}
}
#[derive(Debug)]
pub struct InvalidBool {
pub input: String,
}
impl FromStr for ResTypeBoolType {
type Err = InvalidBool;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"true" => Self::True,
"false" => Self::False,
_ => Err(InvalidBool {
input: s.to_string(),
})?,
})
}
}
#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq)]
pub struct ARGB8 {
pub aarrggbb: u32,
}
impl Readable for ARGB8 {
type Args = ();
fn read<R: std::io::Read + std::io::Seek>(
reader: &mut R,
_args: Self::Args,
) -> StreamResult<Self> {
Ok(Self {
aarrggbb: u32::read_no_opts(reader).add_context(|| "read ARGB8")?,
})
}
}
impl Writeable for ARGB8 {
type Args = ();
fn write<W: std::io::Write + std::io::Seek>(
self,
writer: &mut W,
_args: Self::Args,
) -> StreamResult<()> {
self.aarrggbb
.write_no_opts(writer)
.add_context(|| "write ARGB8")
}
}
impl ARGB8 {
pub fn new(alpha: u32, red: u32, green: u32, blue: u32) -> Self {
Self {
aarrggbb: blue | (green << 8) | (red << 16) | (alpha << 24),
}
}
pub fn alpha(&self) -> u8 {
(self.aarrggbb >> 24) as u8
}
pub fn red(&self) -> u8 {
(self.aarrggbb >> 16) as u8
}
pub fn green(&self) -> u8 {
(self.aarrggbb >> 8) as u8
}
pub fn blue(&self) -> u8 {
(self.aarrggbb) as u8
}
}
#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq)]
pub struct RGB8 {
pub rrggbb: u32,
}
impl Readable for RGB8 {
type Args = ();
fn read<R: std::io::Read + std::io::Seek>(
reader: &mut R,
_args: Self::Args,
) -> StreamResult<Self> {
Ok(Self {
rrggbb: u32::read_no_opts(reader).add_context(|| "read RGB8")?,
})
}
}
impl Writeable for RGB8 {
type Args = ();
fn write<W: std::io::Write + std::io::Seek>(
self,
writer: &mut W,
_args: Self::Args,
) -> StreamResult<()> {
self.rrggbb
.write_no_opts(writer)
.add_context(|| "read RGB8")
}
}
impl RGB8 {
pub fn new(red: u32, green: u32, blue: u32) -> Self {
Self {
rrggbb: blue | (green << 8) | (red << 16),
}
}
pub fn red(&self) -> u8 {
(self.rrggbb >> 16) as u8
}
pub fn green(&self) -> u8 {
(self.rrggbb >> 8) as u8
}
pub fn blue(&self) -> u8 {
(self.rrggbb) as u8
}
}
#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq)]
pub struct ARGB4 {
pub argb: u32,
}
impl Readable for ARGB4 {
type Args = ();
fn read<R: std::io::Read + std::io::Seek>(
reader: &mut R,
_args: Self::Args,
) -> StreamResult<Self> {
Ok(Self {
argb: u32::read_no_opts(reader).add_context(|| "read ARGB4")?,
})
}
}
impl Writeable for ARGB4 {
type Args = ();
fn write<W: std::io::Write + std::io::Seek>(
self,
writer: &mut W,
_args: Self::Args,
) -> StreamResult<()> {
self.argb
.write_no_opts(writer)
.add_context(|| "write ARGB4")
}
}
impl ARGB4 {
pub fn new(alpha: u32, red: u32, green: u32, blue: u32) -> Self {
Self {
argb: blue | (green << 4) | (red << 8) | (alpha << 12),
}
}
pub fn alpha(&self) -> u8 {
(self.argb >> 12) as u8 & 0xF
}
pub fn red(&self) -> u8 {
(self.argb >> 8) as u8 & 0xF
}
pub fn green(&self) -> u8 {
(self.argb >> 4) as u8 & 0xF
}
pub fn blue(&self) -> u8 {
(self.argb) as u8 & 0xF
}
}
#[derive(Debug, PartialEq, Clone, Copy, Hash, Eq)]
pub struct RGB4 {
pub rgb: u32,
}
impl Readable for RGB4 {
type Args = ();
fn read<R: std::io::Read + std::io::Seek>(
reader: &mut R,
_args: Self::Args,
) -> StreamResult<Self> {
Ok(Self {
rgb: u32::read_no_opts(reader).add_context(|| "read RGB4")?,
})
}
}
impl Writeable for RGB4 {
type Args = ();
fn write<W: std::io::Write + std::io::Seek>(
self,
writer: &mut W,
_args: Self::Args,
) -> StreamResult<()> {
self.rgb.write_no_opts(writer).add_context(|| "write RGB4")
}
}
impl RGB4 {
pub fn new(red: u32, green: u32, blue: u32) -> Self {
Self {
rgb: blue | (green << 4) | (red << 8),
}
}
pub fn red(&self) -> u8 {
(self.rgb >> 8) as u8 & 0xF
}
pub fn green(&self) -> u8 {
(self.rgb >> 4) as u8 & 0xF
}
pub fn blue(&self) -> u8 {
(self.rgb) as u8 & 0xF
}
}
impl From<bool> for ResTypeBoolType {
fn from(value: bool) -> Self {
match value {
true => ResTypeBoolType::True,
false => ResTypeBoolType::False,
}
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum ResValueType {
Null(ResTypeNullType),
Reference(ResTableRef),
Attribute(ResTableRef),
String(ResStringPoolRef),
Float(f32),
Dimension(u32),
Fraction(u32),
DynamicReference(ResTableRef),
DynamicAttribute(ResTableRef),
IntDec(u32),
IntHex(u32),
IntBoolean(ResTypeBoolType),
IntColorARGB8(ARGB8),
IntColorRGB8(RGB8),
IntColorARGB4(ARGB4),
IntColorRGB4(RGB4),
}
impl Default for ResValueType {
fn default() -> Self {
Self::Null(ResTypeNullType::Empty)
}
}
impl Readable for ResValueType {
type Args = ();
fn read<R: std::io::Read + std::io::Seek>(
reader: &mut R,
_args: Self::Args,
) -> StreamResult<Self> {
let res_type =
u8::read_no_opts(reader).add_context(|| "read res_type id for ResValueType")?;
Ok(match res_type {
0x0 => ResValueType::Null(ResTypeNullType::read_no_opts(reader)?),
0x1 => ResValueType::Reference(ResTableRef::read_no_opts(reader)?),
0x2 => ResValueType::Attribute(
ResTableRef::read_no_opts(reader)
.add_context(|| "read attribute for ResValueType")?,
),
0x3 => ResValueType::String(ResStringPoolRef::read_no_opts(reader)?),
0x4 => ResValueType::Float(
f32::read_no_opts(reader).add_context(|| "read float for ResValueType")?,
),
0x5 => ResValueType::Dimension(
u32::read_no_opts(reader).add_context(|| "read dimension for ResValueType")?,
),
0x6 => ResValueType::Fraction(
u32::read_no_opts(reader).add_context(|| "read fraction for ResValueType")?,
),
0x7 => ResValueType::DynamicReference(ResTableRef::read_no_opts(reader)?),
0x8 => ResValueType::DynamicAttribute(
ResTableRef::read_no_opts(reader)
.add_context(|| "read dynamic attribute for ResValueType")?,
),
0x10 => ResValueType::IntDec(
u32::read_no_opts(reader).add_context(|| "read int dec for ResValueType")?,
),
0x11 => ResValueType::IntHex(
u32::read_no_opts(reader).add_context(|| "read int hex for ResValueType")?,
),
0x12 => ResValueType::IntBoolean(ResTypeBoolType::read_no_opts(reader)?),
0x1c => ResValueType::IntColorARGB8(ARGB8::read_no_opts(reader)?),
0x1d => ResValueType::IntColorRGB8(RGB8::read_no_opts(reader)?),
0x1e => ResValueType::IntColorARGB4(ARGB4::read_no_opts(reader)?),
0x1f => ResValueType::IntColorRGB4(RGB4::read_no_opts(reader)?),
v => {
return Err(StreamError::new_string_context(
format!("invalid res_type {v}"),
reader.stream_position()?,
"match res_type for ResValueType",
));
}
})
}
}
impl Writeable for ResValueType {
type Args = ();
fn write<W: std::io::Write + std::io::Seek>(
self,
writer: &mut W,
_args: Self::Args,
) -> StreamResult<()> {
let pos = writer.stream_position()?;
writer.seek_relative(1)?;
let res_type: u8 = match self {
Self::Null(v) => {
v.write_no_opts(writer)?;
0
}
Self::Reference(v) => {
v.write_no_opts(writer)?;
0x1
}
Self::Attribute(v) => {
v.write_no_opts(writer)?;
0x2
}
Self::String(v) => {
v.write_no_opts(writer)?;
0x3
}
Self::Float(v) => {
v.write_no_opts(writer)?;
0x4
}
Self::Dimension(v) => {
v.write_no_opts(writer)?;
0x5
}
Self::Fraction(v) => {
v.write_no_opts(writer)?;
0x6
}
Self::DynamicReference(v) => {
v.write_no_opts(writer)?;
0x7
}
Self::DynamicAttribute(v) => {
v.write_no_opts(writer)?;
0x8
}
Self::IntDec(v) => {
v.write_no_opts(writer)?;
0x10
}
Self::IntHex(v) => {
v.write_no_opts(writer)?;
0x11
}
Self::IntBoolean(v) => {
v.write_no_opts(writer)?;
0x12
}
Self::IntColorARGB8(v) => {
v.write_no_opts(writer)?;
0x1c
}
Self::IntColorRGB8(v) => {
v.write_no_opts(writer)?;
0x1d
}
Self::IntColorARGB4(v) => {
v.write_no_opts(writer)?;
0x1e
}
Self::IntColorRGB4(v) => {
v.write_no_opts(writer)?;
0x1f
}
};
writer.seek(std::io::SeekFrom::Start(pos))?;
res_type
.write_no_opts(writer)
.add_context(|| "write res_type for ResValueType")?;
Ok(writer.seek_relative(4)?)
}
}