use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
resources::DotNetResourceEncoder,
tables::{
CodedIndex, CodedIndexType, ManifestResourceAttributes, ManifestResourceRaw,
TableDataOwned, TableId,
},
token::Token,
},
Error, Result,
};
#[derive(Debug, Clone, Copy)]
enum ResourceImplementationTarget {
Embedded,
File(u32),
AssemblyRef(u32),
}
#[derive(Debug, Clone)]
pub struct ManifestResourceBuilder {
name: Option<String>,
flags: u32,
offset: u32,
implementation: Option<ResourceImplementationTarget>,
resource_data: Option<Vec<u8>>,
resource_encoder: Option<DotNetResourceEncoder>,
}
impl Default for ManifestResourceBuilder {
fn default() -> Self {
Self::new()
}
}
impl ManifestResourceBuilder {
#[must_use]
pub fn new() -> Self {
Self {
name: None,
flags: ManifestResourceAttributes::PUBLIC.bits(),
offset: 0,
implementation: None, resource_data: None,
resource_encoder: None,
}
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn flags(mut self, flags: u32) -> Self {
self.flags = flags;
self
}
#[must_use]
pub fn public(mut self) -> Self {
self.flags |= ManifestResourceAttributes::PUBLIC.bits();
self.flags &= !ManifestResourceAttributes::PRIVATE.bits();
self
}
#[must_use]
pub fn private(mut self) -> Self {
self.flags |= ManifestResourceAttributes::PRIVATE.bits();
self.flags &= !ManifestResourceAttributes::PUBLIC.bits();
self
}
#[must_use]
pub fn offset(mut self, offset: u32) -> Self {
self.offset = offset;
self
}
#[must_use]
pub fn implementation_file(mut self, file_row: u32) -> Self {
self.implementation = Some(ResourceImplementationTarget::File(file_row));
self
}
#[must_use]
pub fn implementation_assembly_ref(mut self, assembly_ref_row: u32) -> Self {
self.implementation = Some(ResourceImplementationTarget::AssemblyRef(assembly_ref_row));
self
}
#[must_use]
pub fn implementation_embedded(mut self) -> Self {
self.implementation = Some(ResourceImplementationTarget::Embedded);
self
}
#[must_use]
pub fn resource_data(mut self, data: &[u8]) -> Self {
self.resource_data = Some(data.to_vec());
self.implementation = Some(ResourceImplementationTarget::Embedded); self
}
#[must_use]
pub fn resource_string(mut self, content: &str) -> Self {
self.resource_data = Some(content.as_bytes().to_vec());
self.implementation = Some(ResourceImplementationTarget::Embedded); self
}
pub fn add_string_resource(mut self, resource_name: &str, content: &str) -> Result<Self> {
let encoder = self
.resource_encoder
.get_or_insert_with(DotNetResourceEncoder::new);
encoder.add_string(resource_name, content)?;
self.implementation = Some(ResourceImplementationTarget::Embedded); Ok(self)
}
pub fn add_binary_resource(mut self, resource_name: &str, data: &[u8]) -> Result<Self> {
let encoder = self
.resource_encoder
.get_or_insert_with(DotNetResourceEncoder::new);
encoder.add_byte_array(resource_name, data)?;
self.implementation = Some(ResourceImplementationTarget::Embedded); Ok(self)
}
pub fn add_xml_resource(mut self, resource_name: &str, xml_content: &str) -> Result<Self> {
let encoder = self
.resource_encoder
.get_or_insert_with(DotNetResourceEncoder::new);
encoder.add_string(resource_name, xml_content)?;
self.implementation = None; Ok(self)
}
pub fn add_text_resource(mut self, resource_name: &str, content: &str) -> Result<Self> {
let encoder = self
.resource_encoder
.get_or_insert_with(DotNetResourceEncoder::new);
encoder.add_string(resource_name, content)?;
self.implementation = None; Ok(self)
}
#[must_use]
pub fn configure_encoder<F>(mut self, configure_fn: F) -> Self
where
F: FnOnce(&mut DotNetResourceEncoder),
{
let encoder = self
.resource_encoder
.get_or_insert_with(DotNetResourceEncoder::new);
configure_fn(encoder);
self.implementation = None; self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let name = self.name.ok_or_else(|| {
Error::ModificationInvalid("Resource name is required for ManifestResource".to_string())
})?;
if name.is_empty() {
return Err(Error::ModificationInvalid(
"Resource name cannot be empty for ManifestResource".to_string(),
));
}
let name_index = assembly.string_get_or_add(&name)?.placeholder();
let implementation = match self.implementation {
Some(ResourceImplementationTarget::File(row)) => {
if row == 0 {
return Err(Error::ModificationInvalid(
"Implementation reference row cannot be 0 for File table".to_string(),
));
}
CodedIndex::new(TableId::File, row, CodedIndexType::Implementation)
}
Some(ResourceImplementationTarget::AssemblyRef(row)) => {
if row == 0 {
return Err(Error::ModificationInvalid(
"Implementation reference row cannot be 0 for AssemblyRef table"
.to_string(),
));
}
CodedIndex::new(TableId::AssemblyRef, row, CodedIndexType::Implementation)
}
Some(ResourceImplementationTarget::Embedded) | None => {
CodedIndex::new(TableId::File, 0, CodedIndexType::Implementation)
}
};
let mut final_offset = self.offset;
if let Some(encoder) = self.resource_encoder {
let encoded_data = encoder.encode_dotnet_format()?;
final_offset = assembly.resource_data_add(&encoded_data);
} else if let Some(data) = self.resource_data {
final_offset = assembly.resource_data_add(&data);
}
let manifest_resource = ManifestResourceRaw {
rid: 0,
token: Token::new(0),
offset: 0,
offset_field: final_offset,
flags: self.flags,
name: name_index,
implementation,
};
assembly.table_row_add(
TableId::ManifestResource,
TableDataOwned::ManifestResource(manifest_resource),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::ChangeRefKind,
metadata::tables::{ManifestResourceAttributes, TableId},
test::factories::table::assemblyref::get_test_assembly,
};
#[test]
fn test_manifest_resource_builder_basic() -> Result<()> {
let mut assembly = get_test_assembly()?;
let resource_ref = ManifestResourceBuilder::new()
.name("MyApp.Resources")
.build(&mut assembly)?;
assert_eq!(
resource_ref.kind(),
ChangeRefKind::TableRow(TableId::ManifestResource)
);
Ok(())
}
#[test]
fn test_manifest_resource_builder_default() -> Result<()> {
let builder = ManifestResourceBuilder::default();
assert!(builder.name.is_none());
assert_eq!(builder.flags, ManifestResourceAttributes::PUBLIC.bits());
assert_eq!(builder.offset, 0);
assert!(builder.resource_data.is_none());
assert!(builder.resource_encoder.is_none());
Ok(())
}
#[test]
fn test_manifest_resource_builder_missing_name() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = ManifestResourceBuilder::new().public().build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Resource name is required"));
Ok(())
}
#[test]
fn test_manifest_resource_builder_empty_name() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = ManifestResourceBuilder::new().name("").build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Resource name cannot be empty"));
Ok(())
}
#[test]
fn test_manifest_resource_builder_public() -> Result<()> {
let mut assembly = get_test_assembly()?;
let resource_ref = ManifestResourceBuilder::new()
.name("PublicResource")
.public()
.build(&mut assembly)?;
assert_eq!(
resource_ref.kind(),
ChangeRefKind::TableRow(TableId::ManifestResource)
);
Ok(())
}
#[test]
fn test_manifest_resource_builder_private() -> Result<()> {
let mut assembly = get_test_assembly()?;
let resource_ref = ManifestResourceBuilder::new()
.name("PrivateResource")
.private()
.build(&mut assembly)?;
assert_eq!(
resource_ref.kind(),
ChangeRefKind::TableRow(TableId::ManifestResource)
);
Ok(())
}
#[test]
fn test_manifest_resource_builder_with_offset() -> Result<()> {
let mut assembly = get_test_assembly()?;
let resource_ref = ManifestResourceBuilder::new()
.name("EmbeddedResource")
.offset(0x1000)
.build(&mut assembly)?;
assert_eq!(
resource_ref.kind(),
ChangeRefKind::TableRow(TableId::ManifestResource)
);
Ok(())
}
#[test]
fn test_manifest_resource_builder_with_flags() -> Result<()> {
let mut assembly = get_test_assembly()?;
let resource_ref = ManifestResourceBuilder::new()
.name("CustomResource")
.flags(ManifestResourceAttributes::PRIVATE.bits())
.build(&mut assembly)?;
assert_eq!(
resource_ref.kind(),
ChangeRefKind::TableRow(TableId::ManifestResource)
);
Ok(())
}
#[test]
fn test_manifest_resource_builder_embedded() -> Result<()> {
let mut assembly = get_test_assembly()?;
let resource_ref = ManifestResourceBuilder::new()
.name("EmbeddedResource")
.implementation_embedded()
.offset(0x2000)
.build(&mut assembly)?;
assert_eq!(
resource_ref.kind(),
ChangeRefKind::TableRow(TableId::ManifestResource)
);
Ok(())
}
#[test]
fn test_manifest_resource_builder_multiple_resources() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref1 = ManifestResourceBuilder::new()
.name("Resource1")
.public()
.build(&mut assembly)?;
let ref2 = ManifestResourceBuilder::new()
.name("Resource2")
.private()
.build(&mut assembly)?;
assert!(!std::sync::Arc::ptr_eq(&ref1, &ref2));
assert_eq!(
ref1.kind(),
ChangeRefKind::TableRow(TableId::ManifestResource)
);
assert_eq!(
ref2.kind(),
ChangeRefKind::TableRow(TableId::ManifestResource)
);
Ok(())
}
#[test]
fn test_manifest_resource_builder_comprehensive() -> Result<()> {
let mut assembly = get_test_assembly()?;
let resource_ref = ManifestResourceBuilder::new()
.name("MyApp.Comprehensive.Resources")
.public()
.offset(0x4000)
.implementation_embedded()
.build(&mut assembly)?;
assert_eq!(
resource_ref.kind(),
ChangeRefKind::TableRow(TableId::ManifestResource)
);
Ok(())
}
#[test]
fn test_manifest_resource_builder_fluent_api() -> Result<()> {
let mut assembly = get_test_assembly()?;
let resource_ref = ManifestResourceBuilder::new()
.name("FluentResource")
.private()
.offset(0x8000)
.build(&mut assembly)?;
assert_eq!(
resource_ref.kind(),
ChangeRefKind::TableRow(TableId::ManifestResource)
);
Ok(())
}
#[test]
fn test_manifest_resource_builder_clone() {
let builder1 = ManifestResourceBuilder::new().name("CloneTest").public();
let builder2 = builder1.clone();
assert_eq!(builder1.name, builder2.name);
assert_eq!(builder1.flags, builder2.flags);
assert_eq!(builder1.offset, builder2.offset);
}
#[test]
fn test_manifest_resource_builder_debug() {
let builder = ManifestResourceBuilder::new().name("DebugResource");
let debug_str = format!("{builder:?}");
assert!(debug_str.contains("ManifestResourceBuilder"));
assert!(debug_str.contains("DebugResource"));
}
#[test]
fn test_manifest_resource_builder_zero_row_file_implementation() -> Result<()> {
let mut assembly = get_test_assembly()?;
let mut builder = ManifestResourceBuilder::new().name("ZeroRowImplementation");
builder.implementation = Some(ResourceImplementationTarget::File(0));
let result = builder.build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Implementation reference row cannot be 0"));
Ok(())
}
#[test]
fn test_manifest_resource_builder_zero_row_assemblyref_implementation() -> Result<()> {
let mut assembly = get_test_assembly()?;
let mut builder = ManifestResourceBuilder::new().name("ZeroRowImplementation");
builder.implementation = Some(ResourceImplementationTarget::AssemblyRef(0));
let result = builder.build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Implementation reference row cannot be 0"));
Ok(())
}
#[test]
fn test_manifest_resource_builder_with_resource_data() -> Result<()> {
let mut assembly = get_test_assembly()?;
let resource_data = b"Hello, World!";
let resource_ref = ManifestResourceBuilder::new()
.name("TextResource")
.resource_data(resource_data)
.build(&mut assembly)?;
assert_eq!(
resource_ref.kind(),
ChangeRefKind::TableRow(TableId::ManifestResource)
);
Ok(())
}
#[test]
fn test_manifest_resource_builder_with_resource_string() -> Result<()> {
let mut assembly = get_test_assembly()?;
let resource_ref = ManifestResourceBuilder::new()
.name("ConfigResource")
.resource_string("key=value\nsetting=option")
.build(&mut assembly)?;
assert_eq!(
resource_ref.kind(),
ChangeRefKind::TableRow(TableId::ManifestResource)
);
Ok(())
}
#[test]
fn test_manifest_resource_builder_with_encoder() -> Result<()> {
let mut assembly = get_test_assembly()?;
let resource_ref = ManifestResourceBuilder::new()
.name("EncodedResources")
.add_string_resource("AppTitle", "My Application")?
.add_string_resource("Version", "1.0.0")?
.build(&mut assembly)?;
assert_eq!(
resource_ref.kind(),
ChangeRefKind::TableRow(TableId::ManifestResource)
);
Ok(())
}
#[test]
fn test_manifest_resource_builder_configure_encoder() -> Result<()> {
let mut assembly = get_test_assembly()?;
let resource_ref = ManifestResourceBuilder::new()
.name("OptimizedResources")
.configure_encoder(|_encoder| {
})
.add_string_resource("Test", "Content")?
.build(&mut assembly)?;
assert_eq!(
resource_ref.kind(),
ChangeRefKind::TableRow(TableId::ManifestResource)
);
Ok(())
}
#[test]
fn test_manifest_resource_builder_mixed_resources() -> Result<()> {
let mut assembly = get_test_assembly()?;
let binary_data = vec![0x01, 0x02, 0x03, 0x04];
let xml_content = r#"<?xml version="1.0"?><config><setting value="test"/></config>"#;
let resource_ref = ManifestResourceBuilder::new()
.name("MixedResources")
.add_string_resource("title", "My App")?
.add_binary_resource("data", &binary_data)?
.add_xml_resource("config.xml", xml_content)?
.build(&mut assembly)?;
assert_eq!(
resource_ref.kind(),
ChangeRefKind::TableRow(TableId::ManifestResource)
);
Ok(())
}
}