Implement MemberInfo, FieldInfo, TypeInfo.DeclaredFields

Rename Type to TypeInfo
Add TypeInfo.CSharpName
Implement some generic types/parameters
Implement arrays
Remove obsolete Il2CppType.GetTypeFromTypeIndex
Implement enhanced Il2CppReflector.GetTypeFromTypeIndex (can create array and generic types on-the-fly from Il2CppType usages)
This commit is contained in:
Katy Coe
2017-11-07 05:31:52 +01:00
parent 6ba60a276f
commit 521f82ed4d
9 changed files with 414 additions and 169 deletions

View File

@@ -1,4 +1,6 @@
public static class DefineConstants
using System.Collections.Generic;
public static class DefineConstants
{
public const int FIELD_ATTRIBUTE_PRIVATE = 0x0001;
public const int FIELD_ATTRIBUTE_PUBLIC = 0x0006;
@@ -17,4 +19,80 @@
public const int TYPE_ATTRIBUTE_SERIALIZABLE = 0x00002000;
public const int PARAM_ATTRIBUTE_OUT = 0x0002;
public const int PARAM_ATTRIBUTE_OPTIONAL = 0x0010;
public static List<string> CSharpTypeString = new List<string>
{
"END",
"void",
"bool",
"char",
"sbyte",
"byte",
"short",
"ushort",
"int",
"uint",
"long",
"ulong",
"float",
"double",
"string",
"PTR", // Processed separately
"BYREF",
"VALUETYPE", // Processed separately
"CLASS", // Processed separately
"T",
"Array", // Processed separately
"GENERICINST", // Processed separately
"TYPEDBYREF",
"None",
"IntPtr",
"UIntPtr",
"None",
"delegate",
"object",
"SZARRAY", // Processed separately
"T",
"CMOD_REQD",
"CMOD_OPT",
"INTERNAL",
};
public static List<string> FullNameTypeString = new List<string>
{
"END",
"System.Void",
"System.Boolean",
"System.Char",
"System.SByte",
"System.Byte",
"System.Int16",
"System.UInt16",
"System.Int32",
"System.UInt32",
"System.Int64",
"System.UInt64",
"System.Single",
"System.Double",
"System.String",
"PTR", // Processed separately
"BYREF",
"System.ValueType", // Processed separately
"CLASS", // Processed separately
"T",
"System.Array", // Processed separately
"GENERICINST", // Processed separately
"TYPEDBYREF",
"None",
"System.IntPtr",
"System.UIntPtr",
"None",
"System.Delegate",
"System.Object",
"SZARRAY", // Processed separately
"T",
"CMOD_REQD",
"CMOD_OPT",
"INTERNAL",
};
}

View File

@@ -8,6 +8,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace Il2CppInspector
@@ -103,18 +104,13 @@ namespace Il2CppInspector
ret = $"{GetTypeName(type)}[]";
}
else {
if ((int)pType.type >= szTypeString.Length)
if ((int)pType.type >= DefineConstants.CSharpTypeString.Count)
ret = "unknow";
else
ret = szTypeString[(int)pType.type];
ret = DefineConstants.CSharpTypeString[(int)pType.type];
}
return ret;
}
public Il2CppType GetTypeFromTypeIndex(int idx) {
return Binary.Types[idx];
}
public int GetFieldOffsetFromIndex(int typeIndex, int fieldIndexInType) {
// Versions from 22 onwards use an array of pointers in fieldOffsets
bool fieldOffsetsArePointers = (Metadata.Version >= 22);
@@ -144,7 +140,7 @@ namespace Il2CppInspector
return null;
var pValue = Metadata.Header.fieldAndParameterDefaultValueDataOffset + def.dataIndex;
Il2CppType type = GetTypeFromTypeIndex(def.typeIndex);
Il2CppType type = Binary.Types[def.typeIndex];
if (pValue == 0)
return null;
@@ -192,43 +188,5 @@ namespace Il2CppInspector
}
return value;
}
private readonly string[] szTypeString =
{
"END",
"void",
"bool",
"char",
"sbyte",
"byte",
"short",
"ushort",
"int",
"uint",
"long",
"ulong",
"float",
"double",
"string",
"PTR",//eg. void*
"BYREF",
"VALUETYPE",
"CLASS",
"T",
"ARRAY",
"GENERICINST",
"TYPEDBYREF",
"None",
"IntPtr",
"UIntPtr",
"None",
"FNPTR",
"object",
"SZARRAY",
"T",
"CMOD_REQD",
"CMOD_OPT",
"INTERNAL",
};
}
}

View File

@@ -1,23 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Il2CppInspector.Reflection
{
public class Il2CppReflector
{
public Il2CppInspector Package { get; }
public List<Assembly> Assemblies { get; } = new List<Assembly>();
public Il2CppReflector(Il2CppInspector package) {
Package = package;
// Create Assembly objects from Il2Cpp package
for (var image = 0; image < package.Metadata.Images.Length; image++)
Assemblies.Add(new Assembly(package, image));
Assemblies.Add(new Assembly(this, image));
}
// Get the assembly in which a type is defined
public Assembly GetAssembly(Type type) => Assemblies.FirstOrDefault(x => x.DefinedTypes.Contains(type));
public Assembly GetAssembly(TypeInfo type) => Assemblies.FirstOrDefault(x => x.DefinedTypes.Contains(type));
// Get a type from its IL2CPP type index
public Type GetTypeFromIndex(int typeIndex) => Assemblies.SelectMany(x => x.DefinedTypes).FirstOrDefault(x => x.Index == typeIndex);
public TypeInfo GetTypeFromIndex(int typeIndex) => Assemblies.SelectMany(x => x.DefinedTypes).FirstOrDefault(x => x.Index == typeIndex);
// Get or generate a type from its IL2CPP binary type usage reference
// (field, return type, generic type parameter etc.)
public TypeInfo GetType(Il2CppType pType, MemberTypes memberType = MemberTypes.All) {
switch (pType.type) {
case Il2CppTypeEnum.IL2CPP_TYPE_CLASS:
case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE:
// Classes defined in the metadata
return GetTypeFromIndex((int) pType.datapoint);
case Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST:
case Il2CppTypeEnum.IL2CPP_TYPE_ARRAY:
case Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY:
case Il2CppTypeEnum.IL2CPP_TYPE_PTR:
case Il2CppTypeEnum.IL2CPP_TYPE_VAR:
// Everything that requires special handling
return new TypeInfo(this, pType, memberType);
default:
// Basic primitive types
if ((int) pType.type >= DefineConstants.FullNameTypeString.Count)
return null;
return Assemblies.SelectMany(x => x.DefinedTypes).First(x => x.FullName == DefineConstants.FullNameTypeString[(int)pType.type]);
}
}
}
}

View File

@@ -6,6 +6,7 @@ namespace Il2CppInspector.Reflection {
public class Assembly
{
// IL2CPP-specific data
public Il2CppReflector Model { get; }
public Il2CppImageDefinition Definition { get; }
public int Index { get; }
@@ -15,27 +16,28 @@ namespace Il2CppInspector.Reflection {
public string FullName { get; }
// Entry point method for the assembly
public MethodInfo EntryPoint { get; }
//public MethodInfo EntryPoint { get; } // TODO
// List of types defined in the assembly
public List<Type> DefinedTypes { get; } = new List<Type>();
public List<TypeInfo> DefinedTypes { get; } = new List<TypeInfo>();
// Get a type from its string name (including namespace)
public Type GetType(string typeName) => DefinedTypes.FirstOrDefault(x => x.FullName == typeName);
public TypeInfo GetType(string typeName) => DefinedTypes.FirstOrDefault(x => x.FullName == typeName);
// Initialize from specified assembly index in package
public Assembly(Il2CppInspector pkg, int imageIndex) {
Definition = pkg.Metadata.Images[imageIndex];
public Assembly(Il2CppReflector model, int imageIndex) {
Model = model;
Definition = Model.Package.Metadata.Images[imageIndex];
Index = Definition.assemblyIndex;
FullName = pkg.Metadata.Strings[Definition.nameIndex];
FullName = Model.Package.Metadata.Strings[Definition.nameIndex];
if (Definition.entryPointIndex != -1) {
// TODO: Generate EntryPoint method from entryPointIndex
}
// Generate types in DefinedTypes from typeStart to typeStart+typeCount-1
for (int t = Definition.typeStart; t < Definition.typeStart + Definition.typeCount; t++)
DefinedTypes.Add(new Type(pkg, t, this));
for (var t = Definition.typeStart; t < Definition.typeStart + Definition.typeCount; t++)
DefinedTypes.Add(new TypeInfo(Model.Package, t, this));
}
}
}

View File

@@ -0,0 +1,63 @@
using System.Reflection;
namespace Il2CppInspector.Reflection {
public class FieldInfo : MemberInfo
{
// IL2CPP-specific data
public Il2CppFieldDefinition Definition { get; }
public int Index { get; }
// Information/flags about the field
public FieldAttributes Attributes { get; }
// Type of field
private readonly Il2CppType fieldType;
public TypeInfo FieldType => Assembly.Model.GetType(fieldType, MemberTypes.Field);
// For the Is* definitions below, see:
// https://docs.microsoft.com/en-us/dotnet/api/system.reflection.fieldinfo.isfamilyandassembly?view=netframework-4.7.1#System_Reflection_FieldInfo_IsFamilyAndAssembly
// True if the field is declared as internal
public bool IsAssembly { get; } // TODO
// True if the field is declared as protected
public bool IsFamily { get; } // TODO
// True if the field is declared as 'protected private' (always false)
public bool IsFamilyAndAssembly => false;
// True if the field is declared as protected public
public bool IsFamilyOrAssembly { get; } // TODO
// True if the field is declared as readonly
public bool IsInitOnly => (Attributes & FieldAttributes.InitOnly) == FieldAttributes.InitOnly;
// True if the field is declared a private
public bool IsPrivate => (Attributes & FieldAttributes.Private) == FieldAttributes.Private;
// True if the field is declared as public
public bool IsPublic => (Attributes & FieldAttributes.Public) == FieldAttributes.Public;
// True if the field is declared as static
public bool IsStatic => (Attributes & FieldAttributes.Static) == FieldAttributes.Static;
public override MemberTypes MemberType { get; }
public FieldInfo(Il2CppInspector pkg, int fieldIndex, TypeInfo declaringType) :
base(declaringType) {
Definition = pkg.Metadata.Fields[fieldIndex];
Index = fieldIndex;
Name = pkg.Metadata.Strings[pkg.Metadata.Fields[fieldIndex].nameIndex];
fieldType = pkg.Binary.Types[Definition.typeIndex];
if ((fieldType.attrs & DefineConstants.FIELD_ATTRIBUTE_PRIVATE) == DefineConstants.FIELD_ATTRIBUTE_PRIVATE)
Attributes |= FieldAttributes.Private;
if ((fieldType.attrs & DefineConstants.FIELD_ATTRIBUTE_PUBLIC) == DefineConstants.FIELD_ATTRIBUTE_PUBLIC)
Attributes |= FieldAttributes.Public;
if ((fieldType.attrs & DefineConstants.FIELD_ATTRIBUTE_STATIC) == DefineConstants.FIELD_ATTRIBUTE_STATIC)
Attributes |= FieldAttributes.Static;
if ((fieldType.attrs & DefineConstants.FIELD_ATTRIBUTE_INIT_ONLY) == DefineConstants.FIELD_ATTRIBUTE_INIT_ONLY)
Attributes |= FieldAttributes.InitOnly;
}
}
}

View File

@@ -4,21 +4,35 @@ using System.Reflection;
namespace Il2CppInspector.Reflection {
public abstract class MemberInfo
{
// Assembly that this member is defined in
public Assembly Assembly { get; set; }
// Assembly that this member is defined in. Only set when MemberType == TypeInfo
public Assembly Assembly { get; }
// Custom attributes for this member
public IEnumerable<CustomAttributeData> CustomAttributes { get; set; } // TODO
public IEnumerable<CustomAttributeData> CustomAttributes { get; } // TODO
// Type that this type is declared in for nested types
public Type DeclaringType { get; set; } // TODO
public TypeInfo DeclaringType { get; }
// What sort of member this is, eg. method, field etc.
public MemberTypes MemberType { get; set; } // TODO
public abstract MemberTypes MemberType { get; }
// Name of the member
public string Name { get; set; }
public virtual string Name { get; protected set; }
// TODO: GetCustomAttributes etc.
// For top-level members in an assembly (ie. non-nested types)
protected MemberInfo(Assembly asm, TypeInfo declaringType = null) {
Assembly = asm;
DeclaringType = declaringType;
}
// For lower level members, eg. fields, properties etc. and nested types
protected MemberInfo(TypeInfo declaringType) {
if (declaringType != null) {
Assembly = declaringType.Assembly;
DeclaringType = declaringType;
}
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Reflection;
namespace Il2CppInspector.Reflection
{
/*
public abstract class MethodBase : MemberInfo
{
// (not code attributes)
@@ -21,11 +22,6 @@ namespace Il2CppInspector.Reflection
// TODO
}
public class FieldInfo : MemberInfo
{
// TODO
}
public class PropertyInfo : MemberInfo
{
// TODO
@@ -35,4 +31,5 @@ namespace Il2CppInspector.Reflection
{
// TODO
}
*/
}

View File

@@ -1,99 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Il2CppInspector.Reflection {
public class Type : MemberInfo
{
// IL2CPP-specific data
public Il2CppTypeDefinition Definition { get; }
public int Index { get; }
// (not code attributes)
// Undefined if the Type represents a generic type parameter
public TypeAttributes Attributes { get; } // TODO
// Type that this type inherits from
public Type BaseType { get; } // TODO
// TODO: ContainsGenericParameters
// Method that the type is declared in if this is a type parameter of a generic method
public MethodBase DeclaringMethod { get; } // TODO
// Gets the type of the object encompassed or referred to by the current array, pointer or reference type
public Type ElementType { get; } // TODO
// Type name including namespace
public string FullName => Namespace + "." + Name;
// TODO: Generic stuff
public bool HasElementType { get; } // TODO
public bool IsAbstract { get; }
public bool IsArray { get; } // TODO
public bool IsByRef { get; } // TODO
public bool IsClass { get; }
public bool IsEnum { get; } // TODO
public bool IsGenericParameter { get; } // TODO
public bool IsGenericType { get; } // TODO
public bool IsGenericTypeDefinition { get; } // TODO
public bool IsInterface { get; }
public bool IsNested { get; } // TODO
public bool IsNestedPrivate { get; } // TODO
public bool IsNestedPublic { get; } // TODO
public bool IsPointer { get; } // TODO
public bool IsPrimitive { get; } // TODO
public bool IsPublic { get; }
public bool IsSealed { get; }
public bool IsSerializable { get; }
public bool IsValueType { get; } // TODO
public string Namespace { get; }
// Number of dimensions of an array
public int GetArrayRank() => throw new NotImplementedException();
public List<ConstructorInfo> Constructors { get; } // TODO
public List<Type> Inerfaces { get; } // TODO
public List<MemberInfo> Members { get; } // TODO
public List<MethodInfo> Methods { get; } // TODO
public List<FieldInfo> Fields { get; } // TODO
public List<Type> NestedTypes { get; } // TODO
public List<PropertyInfo> Properties { get; } // TODO
// TODO: Custom attribute stuff
public string[] GetEnumNames() => throw new NotImplementedException();
public Type GetEnumUnderlyingType() => throw new NotImplementedException();
public Array GetEnumValues() => throw new NotImplementedException();
// TODO: Event stuff
// TODO: Generic stuff
// Initialize from specified type index in package
public Type(Il2CppInspector pkg, int typeIndex, Assembly owner) {
Assembly = owner;
Definition = pkg.Metadata.Types[typeIndex];
Index = typeIndex;
Name = pkg.Metadata.Strings[Definition.nameIndex];
Namespace = pkg.Metadata.Strings[Definition.namespaceIndex];
IsSerializable = (Definition.flags & DefineConstants.TYPE_ATTRIBUTE_SERIALIZABLE) != 0;
IsPublic = (Definition.flags & DefineConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == DefineConstants.TYPE_ATTRIBUTE_PUBLIC;
IsAbstract = (Definition.flags & DefineConstants.TYPE_ATTRIBUTE_ABSTRACT) != 0;
IsSealed = (Definition.flags & DefineConstants.TYPE_ATTRIBUTE_SEALED) != 0;
IsInterface = (Definition.flags & DefineConstants.TYPE_ATTRIBUTE_INTERFACE) != 0;
IsClass = !IsInterface;
}
}
}

View File

@@ -0,0 +1,202 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Il2CppInspector.Reflection {
public class TypeInfo : MemberInfo
{
// IL2CPP-specific data
public Il2CppTypeDefinition Definition { get; }
public int Index { get; }
// Information/flags about the type
// Undefined if the Type represents a generic type parameter
public TypeAttributes Attributes { get; } // TODO
// Type that this type inherits from
public TypeInfo BaseType { get; } // TODO
// True if the type contains unresolved generic type parameters
public bool ContainsGenericParameters { get; }
// C# colloquial name of the type (if available)
public string CSharpName {
get {
var s = Namespace + "." + base.Name;
var i = DefineConstants.FullNameTypeString.IndexOf(s);
var n = (i != -1 ? DefineConstants.CSharpTypeString[i] : base.Name);
if (IsArray)
n = ElementType.CSharpName;
var g = (GenericTypeParameters != null ? "<" + string.Join(", ", GenericTypeParameters.Select(x => x.CSharpName)) + ">" : "");
return (IsPointer ? "void *" : "") + n + g + (IsArray ? "[]" : "");
}
}
public List<ConstructorInfo> DeclaredConstructors { get; } // TODO
public List<EventInfo> DeclaredEvents { get; } // TODO
public List<FieldInfo> DeclaredFields { get; } = new List<FieldInfo>();
public List<MemberInfo> DeclaredMembers { get; } // TODO
public List<MethodInfo> DeclaredMethods { get; } // TODO
public List<TypeInfo> DeclaredNestedTypes { get; } // TODO
public List<PropertyInfo> DeclaredProperties { get; } // TODO
// Method that the type is declared in if this is a type parameter of a generic method
public MethodBase DeclaringMethod { get; } // TODO
// Gets the type of the object encompassed or referred to by the current array, pointer or reference type
public TypeInfo ElementType { get; }
// Type name including namespace
public string FullName => (IsPointer? "void *" : "")
+ Namespace
+ (Namespace.Length > 0? "." : "")
+ base.Name
+ (GenericTypeParameters != null ? "<" + string.Join(", ", GenericTypeParameters.Select(x => x.Name)) + ">" : "")
+ (IsArray? "[]" : "");
// TODO: Alot of other generics stuff
public List<TypeInfo> GenericTypeParameters { get; }
public bool HasElementType => ElementType != null;
public bool IsAbstract { get; }
public bool IsArray { get; }
public bool IsByRef { get; } // TODO
public bool IsClass { get; }
public bool IsEnum { get; } // TODO
public bool IsGenericParameter { get; }
public bool IsGenericType { get; } // TODO
public bool IsGenericTypeDefinition { get; } // TODO
public bool IsInterface { get; }
public bool IsNested { get; } // TODO
public bool IsNestedPrivate { get; } // TODO
public bool IsNestedPublic { get; } // TODO
public bool IsPointer { get; } // TODO
public bool IsPrimitive { get; } // TODO
public bool IsPublic { get; }
public bool IsSealed { get; }
public bool IsSerializable { get; }
public bool IsValueType { get; } // TODO
public override MemberTypes MemberType { get; }
public override string Name {
get => (IsPointer ? "void *" : "")
+ base.Name
+ (GenericTypeParameters != null? "<" + string.Join(", ", GenericTypeParameters.Select(x => x.Name)) + ">" : "")
+ (IsArray ? "[]" : "");
protected set => base.Name = value;
}
public string Namespace { get; }
// Number of dimensions of an array
private readonly int arrayRank;
public int GetArrayRank() => arrayRank;
// TODO: Custom attribute stuff
public string[] GetEnumNames() => throw new NotImplementedException();
public TypeInfo GetEnumUnderlyingType() => throw new NotImplementedException();
public Array GetEnumValues() => throw new NotImplementedException();
// TODO: Event stuff
// TODO: Generic stuff
// Initialize from specified type index in metadata
public TypeInfo(Il2CppInspector pkg, int typeIndex, Assembly owner) :
base(owner) {
Definition = pkg.Metadata.Types[typeIndex];
Index = typeIndex;
Namespace = pkg.Metadata.Strings[Definition.namespaceIndex];
Name = pkg.Metadata.Strings[pkg.Metadata.Types[typeIndex].nameIndex];
IsSerializable = (Definition.flags & DefineConstants.TYPE_ATTRIBUTE_SERIALIZABLE) != 0;
IsPublic = (Definition.flags & DefineConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == DefineConstants.TYPE_ATTRIBUTE_PUBLIC;
IsAbstract = (Definition.flags & DefineConstants.TYPE_ATTRIBUTE_ABSTRACT) != 0;
IsSealed = (Definition.flags & DefineConstants.TYPE_ATTRIBUTE_SEALED) != 0;
IsInterface = (Definition.flags & DefineConstants.TYPE_ATTRIBUTE_INTERFACE) != 0;
IsClass = !IsInterface;
for (var f = Definition.fieldStart; f < Definition.fieldStart + Definition.field_count; f++)
DeclaredFields.Add(new FieldInfo(pkg, f, this));
MemberType = MemberTypes.TypeInfo;
}
// Initialize type from binary usage
public TypeInfo(Il2CppReflector model, Il2CppType pType, MemberTypes memberType) : base(null) {
var image = model.Package.Binary.Image;
IsNested = true;
MemberType = memberType;
// Generic type unresolved and concrete instance types
if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST) {
var generic = image.ReadMappedObject<Il2CppGenericClass>(pType.datapoint);
var genericTypeDef = model.GetTypeFromIndex(generic.typeDefinitionIndex);
Namespace = genericTypeDef.Namespace;
Name = genericTypeDef.Name;
// TODO: Generic* properties and ContainsGenericParameters
// Get the instantiation
var genericInstance = image.ReadMappedObject<Il2CppGenericInst>(generic.context.class_inst);
// Get list of pointers to type parameters (both unresolved and concrete)
var genericTypeParameters = image.ReadMappedArray<uint>(genericInstance.type_argv, (int)genericInstance.type_argc);
GenericTypeParameters = new List<TypeInfo>();
foreach (var pArg in genericTypeParameters) {
var argType = image.ReadMappedObject<Il2CppType>(pArg);
// TODO: Detect whether unresolved or concrete (add concrete to GenericTypeArguments instead)
// TODO: GenericParameterPosition etc. in types we generate here
GenericTypeParameters.Add(model.GetType(argType)); // TODO: Fix MemberType here
}
IsClass = true;
IsInterface = !IsClass;
}
// Array with known dimensions and bounds
if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_ARRAY) {
var descriptor = image.ReadMappedObject<Il2CppArrayType>(pType.datapoint);
var elementType = image.ReadMappedObject<Il2CppType>(descriptor.etype);
ElementType = model.GetType(elementType);
Namespace = ElementType.Namespace;
Name = ElementType.Name;
IsArray = true;
arrayRank = descriptor.rank;
}
// Dynamically allocated array
if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY) {
var elementType = image.ReadMappedObject<Il2CppType>(pType.datapoint);
ElementType = model.GetType(elementType);
Namespace = ElementType.Namespace;
Name = ElementType.Name;
IsArray = true;
}
// Unresolved generic type variable
if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_VAR) {
ContainsGenericParameters = true;
IsClass = true;
IsGenericParameter = true;
Name = "T"; // TODO: Don't hardcode parameter name
// TODO: GenericTypeParameters?
}
// Pointer type
IsPointer = (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_PTR);
}
}
}