Merge branch 'main' of git.mineau.eu:histausse/androscalpel

This commit is contained in:
Jean-Marie 'Histausse' Mineau 2025-02-20 17:50:32 +01:00
commit bb9e0cdedd
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
13 changed files with 135 additions and 114 deletions

View file

@ -154,7 +154,7 @@ impl Apk {
{
info!(
"Unexpected flags found in class_def_item.access_flags for {}: 0x{:x}",
String::from(descriptor.get_name()),
descriptor.get_name().__str__(),
class_item.access_flags
);
}
@ -229,7 +229,7 @@ impl Apk {
"Inconsistant static_values array found in {}: \
|static_values| = {}, |static_fields| = {}, \
|static_values| should be <= |static_fields|",
String::from(&descriptor.get_name()),
&descriptor.get_name().__str__(),
values.len(),
static_fields_list.len()
));
@ -266,7 +266,7 @@ impl Apk {
info!(
"Annotation for field {} found in class {}, dropping it",
field_id.__str__(),
String::from(descriptor.get_name()),
descriptor.get_name().__str__(),
);
}
instance_fields
@ -275,8 +275,8 @@ impl Apk {
static_fields
.entry(field_id.clone())
.and_modify(|field| field.annotations = annotations.clone());
if instance_fields.get(&field_id).is_none()
&& static_fields.get(&field_id).is_none()
if !instance_fields.contains_key(&field_id)
&& !static_fields.contains_key(&field_id)
{
info!(
"Annotation found for field {} but could not find the field definition, dropping it",
@ -299,7 +299,7 @@ impl Apk {
info!(
"Annotation for method {} found in class {}, dropping it",
method_id.__str__(),
String::from(descriptor.get_name()),
descriptor.get_name().__str__(),
);
}
direct_methods
@ -308,8 +308,8 @@ impl Apk {
virtual_methods
.entry(method_id.clone())
.and_modify(|method| method.annotations = annotations.clone()); // TODO = or append?
if direct_methods.get(&method_id).is_none()
&& virtual_methods.get(&method_id).is_none()
if !direct_methods.contains_key(&method_id)
&& !virtual_methods.contains_key(&method_id)
{
info!(
"Annotation found for method {} but could not find the method definition, dropping it",
@ -338,7 +338,7 @@ impl Apk {
info!(
"Annotation for parameter of method {} found in class {}, dropping it",
method_id.__str__(),
String::from(descriptor.get_name()),
descriptor.get_name().__str__(),
);
}
direct_methods
@ -347,8 +347,8 @@ impl Apk {
virtual_methods
.entry(method_id.clone())
.and_modify(|method| method.parameters_annotations = annotations_list.clone());
if direct_methods.get(&method_id).is_none()
&& virtual_methods.get(&method_id).is_none()
if !direct_methods.contains_key(&method_id)
&& !virtual_methods.contains_key(&method_id)
{
info!(
"Annotation found for parameter of method {} but could not find the method definition, dropping it",
@ -654,8 +654,8 @@ impl Apk {
(false, false, true) => FieldVisibility::Protected,
(false, false, false) => FieldVisibility::None_,
(pbl, prv, prt) => {
let class: String = descriptor.class_.0.into();
let name: String = descriptor.name.into();
let class: String = descriptor.class_.0.__str__();
let name: String = descriptor.name.__str__();
return Err(anyhow!(
"Inconsistant visiblity found in {class}.{name}: \
(public: {pbl}, private: {prv}, protected: {prt})"
@ -2812,7 +2812,7 @@ impl Apk {
.name_idx
.map(|idx| dex.get_string(idx))
.transpose()?
.map(|str| DexString(str).into()),
.map(DexString),
type_: val
.type_idx
.map(|idx| Self::get_id_type_from_idx(idx as usize, dex))
@ -2821,7 +2821,7 @@ impl Apk {
.sig_idx
.map(|idx| dex.get_string(idx))
.transpose()?
.map(|str| DexString(str).into()),
.map(DexString),
},
DebugInfo::EndLocal { reg, .. } => Instruction::DebugEndLocal { reg },
DebugInfo::PrologueEnd { .. } => Instruction::DebugEndPrologue {},
@ -2832,7 +2832,7 @@ impl Apk {
file: source_file_idx
.map(|idx| dex.get_string(idx))
.transpose()?
.map(|str| DexString(str).into()),
.map(DexString),
},
DebugInfo::SetLineNumber { line_num, .. } => Instruction::DebugLine {
number: line_num as usize,
@ -3269,14 +3269,7 @@ impl Apk {
if !self.dex_files.contains_key(&file) {
self.dex_files.insert(file.clone(), DexFile::default());
}
if self
.dex_files
.get(&file)
.unwrap()
.classes
.get(&id)
.is_some()
{
if self.dex_files.get(&file).unwrap().classes.contains_key(&id) {
bail!("class {} already exists in {}", id.__str__(), &file);
}
self.dex_files
@ -3298,7 +3291,7 @@ impl Apk {
let class = if let Some(dex_file) = dex_file {
self.dex_files
.get_mut(&dex_file.to_string())
.get_mut(dex_file)
.with_context(|| format!("file {} not found in apk", dex_file))?
.classes
.get_mut(&method_id.class_)
@ -3345,7 +3338,7 @@ impl Apk {
pub fn remove_class(&mut self, class: &IdType, dex_file: Option<&str>) -> Result<()> {
if let Some(dex_file) = dex_file {
self.dex_files
.get_mut(&dex_file.to_string())
.get_mut(dex_file)
.with_context(|| format!("file {} not found in apk", dex_file))?
.classes
.remove(class);

View file

@ -109,15 +109,15 @@ impl Class {
}
pub fn __str__(&self) -> String {
let name: String = (&self.descriptor.get_name()).into();
let name: String = self.descriptor.get_name().__str__();
let file = if let Some(file) = &self.source_file {
let file: String = file.into();
let file: String = file.__str__();
format!(" defined in {file}\n")
} else {
"".into()
};
let superclass = if let Some(spcl) = &self.superclass {
let spcl: String = spcl.get_name().into();
let spcl: String = spcl.get_name().__str__();
format!(" extends: {spcl}\n")
} else {
"".into()
@ -127,7 +127,7 @@ impl Class {
} else {
let mut interfaces: String = " implements:\n".into();
for it in &self.interfaces {
let it: String = it.get_name().into();
let it: String = it.get_name().__str__();
interfaces += &format!(" {it}\n");
}
interfaces
@ -137,7 +137,7 @@ impl Class {
}
pub fn __repr__(&self) -> String {
let name: String = (&self.descriptor.get_name()).into();
let name: String = self.descriptor.get_name().__str__();
format!("Class({name})")
}

View file

@ -1,6 +1,7 @@
//! Representation of a method.
use anyhow::anyhow;
use log::debug;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
@ -232,7 +233,7 @@ impl Code {
for ins in &self.insns {
match ins {
Instruction::Label { name } => {
if used_labels.get(name).is_none() {
if !used_labels.contains(name) {
continue;
}
let new_label_id = if last_ins_was_a_label {
@ -250,8 +251,8 @@ impl Code {
}
for label in &used_labels {
if new_labels.get(label).is_none() {
println!("{label} use but not in new_labels");
if !new_labels.contains_key(label) {
debug!("{label} use but not in new_labels");
}
}
@ -455,7 +456,7 @@ impl Code {
new_insns.push(Instruction::Switch { branches, reg });
}
Instruction::Label { name } => {
if used_labels.get(&name).is_none() {
if !used_labels.contains(&name) {
//println!("{name} not used");
continue;
}

View file

@ -216,9 +216,10 @@ impl IdMethodType {
/// Compute the format for the shorty as described in
/// <https://source.android.com/docs/core/runtime/dex-format#shortydescriptor>
pub fn compute_shorty(return_type: &IdType, parameters: &[IdType]) -> DexString {
let mut shorty: String = return_type.get_shorty().into();
// TODO: computing on dex string instead of string? that a lot of doubious conversion
let mut shorty: String = return_type.get_shorty().__str__();
for ty in parameters {
let ty: String = ty.get_shorty().into();
let ty: String = ty.get_shorty().__str__();
shorty.push_str(&ty);
}
shorty.into()
@ -441,11 +442,11 @@ impl IdType {
}
pub fn __str__(&self) -> String {
(&self.0).into()
self.0.__str__()
}
pub fn __repr__(&self) -> String {
let name: String = (&self.0).into();
let name: String = self.0.__str__();
format!("IdType(\"{name}\")")
}
@ -562,7 +563,7 @@ impl IdType {
{
Ok(())
} else {
let format: String = (&self.0).into();
let format: String = self.0.__str__();
Err(anyhow!("{format} is not a valid type"))
}
}
@ -744,15 +745,15 @@ impl IdField {
}
pub fn __str__(&self) -> String {
let class: String = self.class_.get_name().into();
let name: String = (&self.name).into();
let ty: String = self.type_.get_name().into();
let class: String = self.class_.get_name().__str__();
let name: String = self.name.__str__();
let ty: String = self.type_.get_name().__str__();
format!("{class}->{name}:{ty}")
}
pub fn __repr__(&self) -> String {
let class: String = self.class_.__repr__();
let name: String = (&self.name).into();
let name: String = self.name.__str__();
let ty: String = self.type_.__repr__();
format!("IdField(\"{name}\", {ty}, {class})")
}

View file

@ -1,4 +1,5 @@
use crate::{Result, Visitable, VisitableMut, Visitor, VisitorMut};
use anyhow::{Context, Error};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::cmp::{Ord, PartialOrd};
use std::collections::HashSet;
@ -26,7 +27,6 @@ impl<V: VisitorMut> VisitableMut<V> for DexString {
impl std::fmt::Debug for DexString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
#[allow(clippy::unnecessary_fallible_conversions)]
if let Ok(string) = TryInto::<String>::try_into(self) {
f.write_str(&format!(
"DexString({}, {:#x})",
@ -117,17 +117,19 @@ impl From<androscalpel_serializer::StringDataItem> for DexString {
}
}
impl From<&DexString> for String {
fn from(DexString(string): &DexString) -> Self {
impl TryFrom<&DexString> for String {
type Error = Error;
fn try_from(DexString(string): &DexString) -> Result<Self> {
string
.try_into()
.unwrap_or(format!("InvalidEncoding:{:x?}", string.data))
.with_context(|| format!("InvalidEncoding:{:x?}", string.data))
}
}
impl From<DexString> for String {
fn from(string: DexString) -> Self {
(&string).into()
impl TryFrom<DexString> for String {
type Error = Error;
fn try_from(string: DexString) -> Result<Self> {
(&string).try_into()
}
}
@ -191,11 +193,15 @@ impl DexString {
}
pub fn __str__(&self) -> String {
self.into()
if let Ok(string) = TryInto::<String>::try_into(self) {
string
} else {
format!("string{:02x?}", self.0.data)
}
}
pub fn __repr__(&self) -> String {
self.into()
format!("{:?}", self)
}
/// Return all strings references in the value.

View file

@ -192,7 +192,7 @@ impl DexWriter {
let new_nb_types = self.type_ids.len()
+ new_types
.iter()
.filter(|ty| self.type_ids.get(ty).is_none())
.filter(|ty| !self.type_ids.contains_key(ty))
.count();
if new_nb_types >= u16::MAX as usize {
return Err(DexWritterError::OutOfSpace(
@ -204,7 +204,7 @@ impl DexWriter {
let new_nb_protos = self.proto_ids.len()
+ new_protos
.iter()
.filter(|proto| self.proto_ids.get(proto).is_none())
.filter(|proto| !self.proto_ids.contains_key(proto))
.count();
if new_nb_protos >= u16::MAX as usize {
return Err(DexWritterError::OutOfSpace(
@ -216,7 +216,7 @@ impl DexWriter {
let new_nb_field_ids = self.field_ids.len()
+ new_field_ids
.iter()
.filter(|field| self.field_ids.get(field).is_none())
.filter(|field| !self.field_ids.contains_key(field))
.count();
if new_nb_field_ids >= u16::MAX as usize {
return Err(DexWritterError::OutOfSpace(
@ -228,7 +228,7 @@ impl DexWriter {
let new_nb_method_ids = self.method_ids.len()
+ new_method_ids
.iter()
.filter(|meth| self.method_ids.get(meth).is_none())
.filter(|meth| !self.method_ids.contains_key(meth))
.count();
if new_nb_method_ids >= u16::MAX as usize {
return Err(DexWritterError::OutOfSpace(
@ -737,7 +737,7 @@ impl DexWriter {
"Could not found type {} of local variable {} in debug info of {} \
in the dex builder",
ty.__repr__(),
name.as_deref().unwrap_or(""),
name.as_ref().map(DexString::__str__).unwrap_or_default(),
method_id.__repr__()
))
})
@ -745,10 +745,10 @@ impl DexWriter {
.map(|idx| *idx as u32),
name_idx: name.as_ref()
.map(|name| {
self.strings.get(&name.as_str().into()).ok_or(anyhow!(
self.strings.get(name).ok_or(anyhow!(
"Could not found string '{}' (name of local variable in debug info of {}) \
in the dex builder",
name.as_str(),
name.__str__(),
method_id.__repr__()
))
})
@ -756,11 +756,11 @@ impl DexWriter {
.map(|idx| *idx as u32),
sig_idx: signature.as_ref()
.map(|sig| {
self.strings.get(&sig.as_str().into()).ok_or(anyhow!(
self.strings.get(sig).ok_or(anyhow!(
"Could not found string '{}' (signature of local variable {} in debug info of {}) \
in the dex builder",
sig,
name.as_deref().unwrap_or(""),
sig.__str__(),
name.as_ref().map(DexString::__str__).unwrap_or_default(),
method_id.__repr__()
))
})
@ -776,7 +776,7 @@ impl DexWriter {
addr: addr as u32,
reg: *reg,
})
.context("Failled to serialize DebugEndLocal information")?,
.context("Failled to seri2846alize DebugEndLocal information")?,
Instruction::DebugEndPrologue {} => debug_builder
.add_info(&DebugInfo::PrologueEnd { addr: addr as u32 })
.context("Failled to serialize DebugEndPrologue information")?,
@ -788,10 +788,10 @@ impl DexWriter {
addr: addr as u32,
source_file_idx: file.as_ref()
.map(|file| {
self.strings.get(&file.as_str().into()).ok_or(anyhow!(
self.strings.get(file).ok_or(anyhow!(
"Could not found string '{}' (name of the source file of part of {}) \
in the dex builder",
file,
file.__str__(),
method_id.__repr__()
))
})
@ -874,7 +874,6 @@ impl DexWriter {
/// Insert annotation associated to a class.
///
/// Insert a class_data_item in the class_data section (in data).
///
/// # Note
@ -2273,12 +2272,12 @@ impl DexWriter {
for (ty, (def, _)) in &self.class_defs {
let mut edges_to = HashSet::new();
if let Some(sup) = def.superclass.as_ref() {
if self.class_defs.get(sup).is_some() {
if self.class_defs.contains_key(sup) {
edges_to.insert(sup);
}
}
for sup in &def.interfaces {
if self.class_defs.get(sup).is_some() {
if self.class_defs.contains_key(sup) {
edges_to.insert(sup);
}
}

View file

@ -23,7 +23,7 @@ impl From<&androscalpel_serializer::HiddenApiFlags> for HiddenApiData {
}
impl From<androscalpel_serializer::HiddenApiFlags> for HiddenApiData {
fn from(flags: androscalpel_serializer::HiddenApiFlags) -> Self {
flags.into()
(&flags).into()
}
}
@ -37,7 +37,7 @@ impl From<&HiddenApiData> for androscalpel_serializer::HiddenApiFlags {
}
impl From<HiddenApiData> for androscalpel_serializer::HiddenApiFlags {
fn from(flags: HiddenApiData) -> Self {
flags.into()
(&flags).into()
}
}

View file

@ -655,9 +655,9 @@ pub enum Instruction {
/// Debug information. Define a local variable associated with a register.
DebugLocal {
reg: u32,
name: Option<String>,
name: Option<DexString>,
type_: Option<IdType>,
signature: Option<String>,
signature: Option<DexString>,
},
/// Debug information. Undefine a local variable associated with a register.
DebugEndLocal { reg: u32 },
@ -666,7 +666,7 @@ pub enum Instruction {
/// Debug information. Indicate the beginning of the Epilogue
DebugBeginEpilogue {},
/// Debug information. Indicate the source file of the following instructions.
DebugSourceFile { file: Option<String> },
DebugSourceFile { file: Option<DexString> },
/// Debug information. Indicate the line number of the following instructions.
DebugLine { number: usize },
}
@ -1233,20 +1233,25 @@ impl<V: Visitor> Visitable<V> for Instruction {
Self::Label { name: _ } => Ok(()),
Self::DebugLocal {
reg: _,
name: _,
type_: Some(type_),
signature: _,
} => v.visit_type(type_),
Self::DebugLocal {
reg: _,
name: _,
type_: None,
signature: _,
} => Ok(()),
name,
type_,
signature,
} => {
if let Some(name) = name {
v.visit_string(name)?;
}
if let Some(type_) = type_ {
v.visit_type(type_)?;
}
if let Some(signature) = signature {
v.visit_string(signature)?;
}
Ok(())
}
Self::DebugEndLocal { reg: _ } => Ok(()),
Self::DebugEndPrologue {} => Ok(()),
Self::DebugBeginEpilogue {} => Ok(()),
Self::DebugSourceFile { file: Some(file) } => v.visit_string(&(file.as_str().into())),
Self::DebugSourceFile { file: Some(file) } => v.visit_string(file),
Self::DebugSourceFile { file: None } => Ok(()),
Self::DebugLine { number: _ } => Ok(()),
}
@ -1900,19 +1905,18 @@ impl<V: VisitorMut> VisitableMut<V> for Instruction {
signature,
} => Ok(Self::DebugLocal {
reg,
name,
name: name.map(|name| v.visit_string(name)).transpose()?,
type_: type_.map(|type_| v.visit_type(type_)).transpose()?,
signature,
signature: signature
.map(|signature| v.visit_string(signature))
.transpose()?,
}),
Self::DebugEndLocal { reg: _ } => Ok(self),
Self::DebugEndPrologue {} => Ok(self),
Self::DebugBeginEpilogue {} => Ok(self),
Self::DebugSourceFile { file: Some(file) } => {
v.visit_string(file.as_str().into())
.map(|file| Self::DebugSourceFile {
file: Some(file.into()),
})
}
Self::DebugSourceFile { file: Some(file) } => v
.visit_string(file)
.map(|file| Self::DebugSourceFile { file: Some(file) }),
Self::DebugSourceFile { file: None } => Ok(self),
Self::DebugLine { number: _ } => Ok(self),
}
@ -2098,7 +2102,7 @@ macro_rules! raw_ins_invoke {
})
} else if consec && len <= 255 {
let a = $args.len() as u8;
let vc = if let Some(vc) = first { vc } else { 0 };
let vc = first.unwrap_or(0);
Ok(InsFormat::Format3RC {
op: $ins_op_3rc,
a,
@ -2123,6 +2127,22 @@ impl Instruction {
Ok(serde_json::from_str(json)?)
}
/// Test if the instruction is a dalvik instruction or a pseudo instruction like a label or a
/// debug info.
pub fn is_pseudo_ins(&self) -> bool {
matches!(
self,
Self::Try { .. }
| Self::Label { .. }
| Self::DebugLocal { .. }
| Self::DebugEndLocal { .. }
| Self::DebugEndPrologue {}
| Self::DebugBeginEpilogue {}
| Self::DebugSourceFile { .. }
| Self::DebugLine { .. }
)
}
pub fn __str__(&self) -> String {
match self {
Self::Nop {} => "nop".into(),
@ -2834,10 +2854,10 @@ impl Instruction {
signature,
} => {
// TODO: check if/how apktool/smali handles empty name and type
let name = name.clone().unwrap_or(String::new());
let name = name.as_ref().map(DexString::__str__).unwrap_or_default();
let ty = type_.as_ref().map(IdType::__str__).unwrap_or(String::new());
if let Some(signature) = signature {
format!(".local {reg}, \"{name}\":{ty}, \"{signature}\"")
format!(".local {reg}, \"{name}\":{ty}, \"{}\"", signature.__str__())
} else {
format!(".local {reg}, \"{name}\":{ty}")
}
@ -2846,7 +2866,9 @@ impl Instruction {
Self::DebugEndPrologue {} => ".prologue".into(),
Self::DebugBeginEpilogue {} => ".epilogue".into(),
// TODO: check if/how apktool/smali handles empty change of src file
Self::DebugSourceFile { file: Some(file) } => format!(".source_file {file}"),
Self::DebugSourceFile { file: Some(file) } => {
format!(".source_file {}", file.__str__())
}
// TODO: find a better representation
Self::DebugSourceFile { file: None } => ".source_file unknown".into(),
Self::DebugLine { number } => format!(".line {number}"),
@ -3644,12 +3666,12 @@ impl Instruction {
type_,
signature,
} => {
let name = name.clone().unwrap_or("None".into());
let name = name.clone().unwrap_or("None".into()).__str__();
let type_ = type_
.as_ref()
.map(IdType::__repr__)
.unwrap_or("None".into());
let signature = signature.clone().unwrap_or("None".into());
let signature = signature.clone().unwrap_or("None".into()).__str__();
format!("Instruction::DebugLoca({reg}, {name}, {type_}, {signature}")
}
Self::DebugEndLocal { reg } => format!("Instruction::DebugEndLocal({reg})"),
@ -3657,7 +3679,7 @@ impl Instruction {
Self::DebugBeginEpilogue {} => "Instruction::DebugBeginEpilogue".into(),
// TODO: check if/how apktool/smali handles empty change of src file
Self::DebugSourceFile { file: Some(file) } => {
format!("Instruction::DebugSourceFile({file})")
format!("Instruction::DebugSourceFile({})", file.__str__())
}
Self::DebugSourceFile { file: None } => "Instruction::DebugSourceFile(None)".into(),
Self::DebugLine { number } => format!("Instruction::DebugLine({number})"),
@ -4465,7 +4487,7 @@ impl Instruction {
})
} else if consec && len <= 255 {
let a = reg_values.len() as u8;
let vc = if let Some(vc) = first { vc } else { 0 };
let vc = first.unwrap_or(0);
Ok(InsFormat::Format3RC {
op: 0x25,
a,
@ -5359,7 +5381,7 @@ impl Instruction {
})
} else if consec && len <= 255 {
let a = args.len() as u8;
let vc = if let Some(vc) = first { vc } else { 0 };
let vc = first.unwrap_or(0);
Ok(InsFormat::Format4RCC {
op: 0xfb,
a,
@ -5436,7 +5458,7 @@ impl Instruction {
})
} else if consec && len <= 255 {
let a = args.len() as u8;
let vc = if let Some(vc) = first { vc } else { 0 };
let vc = first.unwrap_or(0);
Ok(InsFormat::Format3RC {
op: 0xfd,
a,

View file

@ -1,6 +1,3 @@
//#![allow(clippy::unnecessary_fallible_conversions)]
// DexString has Into<String> but it's only for
// python, TryInto should be prefered
use anyhow::Result;
#[cfg(feature = "python")]

View file

@ -84,7 +84,7 @@ fn get_class_dex<'a, 'b>(name: &'a str, dex: &'b DexFileReader) -> Option<&'b Cl
fn get_method_code_dex(name: &str, dex: &DexFileReader) -> Option<CodeItem> {
let method = IdMethod::from_smali(name).unwrap();
let class_name: String = (&method.class_.0).into();
let class_name: String = (&method.class_.0).try_into().unwrap();
let class = get_class_dex(&class_name, dex);
let class = if let Some(class) = class {
class
@ -653,7 +653,9 @@ fn test_hidden_api() {
}
for cls in apk.dex_files.get("classes.dex").unwrap().classes.keys() {
assert!(
apktool_result.get::<String>((&cls.0).into()).is_some(),
apktool_result
.get::<String>((&cls.0).try_into().unwrap())
.is_some(),
"{} not found in core-oj-33_hiddenapi.json",
cls.__str__()
);

View file

@ -100,8 +100,8 @@ impl CodeItem {
addresses.push(try_.start_addr);
addresses.push(try_.start_addr + try_.insn_count as u32);
}
for handler in &self.handlers {
for catch in &handler.list {
if let Some(handlers) = &self.handlers {
for catch in &handlers.list {
for EncodedTypeAddrPair {
addr: Uleb128(addr),
..

View file

@ -55,7 +55,7 @@ use syn::{
/// An enum can define ONE variant as the default variant. This variant is a catch all,
/// and MUST be have named field or unnamed field (no unit variant!) and:
/// - The first field of named fields variant must be named `prefix` and of the type
/// `prefix_type`.
/// `prefix_type`.
/// - The first field of unnamed fields variant must be of the type `prefix_type`.
///
/// The first field of the default variant store the prefix of the variant.

View file

@ -1,8 +1,8 @@
use apk_frauder::ZipFileReader;
use std::collections::HashMap;
use std::env;
//use std::collections::HashMap;
//use std::env;
use std::fs::File;
use std::io::Cursor;
//use std::io::Cursor;
fn main() {
/*