Clases de utilidades para .NET 4.0

Revela aquí tus conocimientos o plasma tus dudas acerca de temas relacionados con los lenguajes de programación. Tan útiles a la hora de hacer nuestras propias herramientas.

Moderador: Faloppa

Clases de utilidades para .NET 4.0

Notapor soywiz » Sab Abr 23, 2011 11:31 am

He creado un proyecto en Google Code con algunas utilidades genéricas para .NET 4.0 hechas en C#.
http://code.google.com/p/csharputils/

El proyecto lo he creado para poder reutilizar algunos componentes que uso tanto en casa como en el trabajo y en el hacking de Tales of Vesperia.

Intentaré ir colocando por aquí cómo usar algunos de los componentes que voy creando que puedan ser de utilidad para la traducción y modificación de juegos.
soywiz
Usuario
Usuario
 
Mensajes: 72
Registrado: Jue Ene 18, 2007 12:29 pm
Ubicación: Valencia

Re: Clases de utilidades para .NET 4.0

Notapor soywiz » Sab Abr 23, 2011 11:45 am

SpaceAssigner1D y SpaceAssigner1DUniqueAllocator
Permite reservar longitudes de una dimensión para su posterior asignación.

Caso práctico:
Tenemos una tabla de punteros que apuntan a textos que están mezclados con cosas y no queremos reconstruir el archivo entero. Hay que ajustarse al espacio que hay (o como mucho añadir textos al final del archivo) y actualizar punteros.

Clases:
http://code.google.com/p/csharputils/so ... igner1D.cs
http://code.google.com/p/csharputils/so ... locator.cs

Unittesting y ejemplo de uso:
http://code.google.com/p/csharputils/so ... r1DTest.cs

Ejemplo:
Código: Seleccionar todo
var OpenedFile = File.Open("file.bin", FileMode.Open);
var PointerTableStream = new SliceStream(OpenedFile, 0x300);
var SpaceAssigner1D = new SpaceAssigner1D();
var SpaceAssigner1DUniqueAllocatorStream = new SpaceAssigner1DUniqueAllocatorStream(SpaceAssigner1D, new SliceStream(OpenedFile, 0x20000));
// Esto crea dos espacios utilizables: 0x1000-0x1020 y 0x1030-0x1100
{
    SpaceAssigner1D.AddAvailable(0x1000, 0x1010);
    SpaceAssigner1D.AddAvailable(0x1010, 0x1020);
    SpaceAssigner1D.AddAvailable(0x1030, 0x1100);
}

// UsedSpace1 y UsedSpace2 coinciden porque al tener el mismo contenido se reutiliza la dirección.
{
    var UsedSpace1 = SpaceAssigner1DUniqueAllocatorStream.AllocateUnique("Prueba", Encoding.UTF8);
    var UsedSpace2 = SpaceAssigner1DUniqueAllocatorStream.AllocateUnique("Prueba", Encoding.UTF8);
    var UsedSpace3 = SpaceAssigner1DUniqueAllocatorStream.AllocateUnique("Una prueba que no entra en el primer espacio por ser excesivamente larga", Encoding.UTF8);
    // UsedSpace2 = UsedSpace1 = SpaceAssigner1D.Space(Min=0x1000, Max=0x1007)
    // UsedSpace2 = SpaceAssigner1D.Space(Min=0x1030, Max=0x1030 + Longitud de la cadena + 1)
    // Además en la posición 0x20000 + 0x1000 se habrá escrito "Prueba\0" y en 0x20000 + 0x1000 "Una prueba que no entra en el primer espacio por ser excesivamente larga\0"

    // Escribimos en la tabla de punteros.
    PointerTableStream.Write((uint)UsedSpace1.Min);
    PointerTableStream.Write((uint)UsedSpace2.Min);
    PointerTableStream.Write((uint)UsedSpace3.Min);
}


Código coloreado en pastebin: http://pastebin.com/d9jzrrg8
soywiz
Usuario
Usuario
 
Mensajes: 72
Registrado: Jue Ene 18, 2007 12:29 pm
Ubicación: Valencia

Re: Clases de utilidades para .NET 4.0

Notapor soywiz » Sab Abr 23, 2011 11:58 am

SliceStream
Permite crear un "recorte" de un Stream. (Utilización de un segmento del Stream manteniendo el Stream original y sin alterar el Position del padre)

Caso práctico:
En muchas ocasiones tenemos que escribir/leer de diferentes partes de un archivo con diferentes sistemas de referencia. Especialmente con archivos de tipo stream o con tablas de punteros. Las tablas de punteros suelen ser relativas y no coinciden con el inicio del archivo. En este caso podemos crear tantos SliceStream como segmentos tengamos que modificar de un archivo.

Clases:
http://code.google.com/p/csharputils/so ... eStream.cs
http://code.google.com/p/csharputils/so ... yStream.cs
soywiz
Usuario
Usuario
 
Mensajes: 72
Registrado: Jue Ene 18, 2007 12:29 pm
Ubicación: Valencia

Re: Clases de utilidades para .NET 4.0

Notapor soywiz » Sab Abr 23, 2011 12:09 pm

Stream extensions
Facilitar el trabajo con archivos binarios y con estructuras nativas.

Caso práctico:
Trabajar con archivos binarios es lo más normal en el mundillo del hacking. Así que todas las operaciones deben ser fáciles. Además dichos archivos suelen contener estructuras como headers o entradas en sistemas de archivos y tablas de punteros.
Lo mejor para saber las posibilidades de estas extensiones es mirar el código fuente.

Clases:
http://code.google.com/p/csharputils/so ... thUtils.cs
http://code.google.com/p/csharputils/so ... ensions.cs

Ejemplo simple:
Código: Seleccionar todo
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct Entry {
    uint Offset;
    uint Length;
    uint Flags;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
    public string Name;
}

// Leería 12 + 0x20 bytes, crearía una estructura Entry y la rellenaría con los datos.
var MyEntry = Stream.ReadStruct<Entry>();

// Escribiría en disco 12 + 0x20 bytes con el contenido de la estructura Entry.
Stream.WriteStruct(MyEntry);


Ejemplo completo de lectura y escritura de los FPS4 del Tales of Vesperia:
Código: Seleccionar todo
public class FPS4 : BasePackage, IDisposable, IEnumerable<FPS4.Entry>
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public struct HeaderStruct
    {
        /// <summary>
        /// Magic of the file its contents should be always "FPS4" for a valid file.
        /// </summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public byte[] Magic;

        /// <summary>
        /// Number of entries for this package.
        /// </summary>
        [Endian(Endianness.BigEndian)]
        public uint ListCount;

        /// <summary>
        /// Start of entry definitions.
        /// Note: Offset relative to the start of the file.
        /// </summary>
        [Endian(Endianness.BigEndian)]
        public uint ListStart;

        /// <summary>
        /// Ends of entry definitions.
        /// Note: Offset relative to the start of the file.
        /// </summary>
        [Endian(Endianness.BigEndian)]
        public uint ListEnd;

        /// <summary>
        /// Size of each entry.
        /// </summary>
        [Endian(Endianness.BigEndian)]
        public ushort EntrySizeof;

        /// <summary>
        /// Format of each entry.
        /// </summary>
        [Endian(Endianness.BigEndian)]
        public ushort EntryFormat;

        /// <summary>
        /// ?
        /// </summary>
        [Endian(Endianness.BigEndian)]
        public uint Unk2;

        /// <summary>
        /// Offset to a string containing the original file path.
        /// Note: Offset relative to the start of the file.
        /// </summary>
        [Endian(Endianness.BigEndian)]
        public uint FilePos;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public struct EntryStruct
    {
        [Endian(Endianness.BigEndian)]
        public uint Offset;

        [Endian(Endianness.BigEndian)]
        public uint LengthSectorAligned;

        [Endian(Endianness.BigEndian)]
        public uint LengthReal;

        //[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
        //public string Name;
    }

    public class Entry : IDisposable
    {
        public FPS4 FPS4;
        public EntryStruct EntryStruct;
        public uint Index;
        public String Name;
        protected Entry _LinkedTo;
        protected Stream _Stream;

        internal Entry(FPS4 FPS4, EntryStruct EntryStruct, String Name)
        {
            this.FPS4 = FPS4;
            this.EntryStruct = EntryStruct;
            this.Name = Name;
            this._Stream = new SliceStream(FPS4.Stream, EntryStruct.Offset, EntryStruct.LengthReal);
        }

        internal Entry(FPS4 FPS4, Stream Stream, String Name)
        {
            this.FPS4 = FPS4;
            this.Name = Name;
            this._Stream = Stream;
        }

        ~Entry()
        {
            Dispose();
        }

        public void Dispose()
        {
            if (_Stream != null)
            {
                _Stream.Dispose();
                _Stream = null;
            }
        }

        public Stream Stream
        {
            get
            {
                return (this._Stream != null) ? new SliceStream(this._Stream) : null;
            }
            set
            {
                this._Stream = value;
                this._LinkedTo = null;
            }
        }

        public bool IsLinked
        {
            get
            {
                return _LinkedTo != null;
            }
        }

        public EntryStruct LinkedEntryStruct
        {
            get
            {
                return (LinkedTo != null) ? LinkedTo.EntryStruct : EntryStruct;
            }
        }

        public Entry LinkedTo
        {
            get
            {
                return this._LinkedTo;
            }
            set
            {
                if (value.FPS4 != this.FPS4) throw (new Exception("Entries from different FPS4"));
                this._LinkedTo = (value._LinkedTo != null) ? value.LinkedTo : value;
                this._Stream = null;
            }
        }

        public override string ToString()
        {
            if (IsLinked)
            {
                return String.Format(
                    "FPS4.Entry(Name='{0}', Linked={1})",
                    Name,
                    LinkedTo
                );
            }
            else
            {
                return String.Format(
                    "FPS4.Entry(Name='{0}', Offset=0x{1}, LengthCompressed={2}, LengthUncompressed={3})",
                    Name,
                    EntryStruct.Offset.ToString("X8"),
                    EntryStruct.LengthSectorAligned,
                    EntryStruct.LengthReal
                );
            }
        }
    }

    public Entry CreateEntry(String Name, Stream Stream)
    {
        return Entries[Name] = new Entry(this, Stream, Name);
    }

    public Entry CreateEntry(String Name, Entry LinkedTo)
    {
        var Entry = new Entry(this, null, Name);
        Entry.LinkedTo = LinkedTo;
        return Entries[Name] = Entry;
    }

    public String OriginalFilePath;
    public Stream Stream;
    public Dictionary<String, Entry> Entries = new Dictionary<String, Entry>();

    public List<Entry> EntryList
    {
        get
        {
            return new List<Entry>(Entries.Values);
        }
    }

    public FPS4()
    {
    }

    public FPS4(Stream Stream)
    {
        Load(Stream);
    }

    ~FPS4()
    {
        Dispose();
    }

    HeaderStruct Header;

    override public void Load(Stream Stream)
    {
        this.Stream = Stream;
        var BinaryReader = new BinaryReader(Stream);

        Header = Stream.ReadStruct<HeaderStruct>();
        if (Encoding.ASCII.GetString(Header.Magic) != "FPS4") throw (new Exception("Invalid Magic"));

        Stream.Position = Header.FilePos;
        OriginalFilePath = Stream.ReadStringz(0x100, Encoding.GetEncoding("Shift-JIS"));

        Stream.Position = Header.ListStart;

        if (Header.ListCount > 10000) throw (new Exception("List too big (" + Header.ListCount + ")"));

        for (uint n = 0; n < Header.ListCount; n++)
        {
            var EntryStruct = Stream.ReadStruct<EntryStruct>();
            var ExtraEntrySizeof = Header.EntrySizeof - 0x0C;
            var IndexName = String.Format("{0}", n);
            var Name = "";

         switch (ExtraEntrySizeof)
            {
            case 0:
            break;
            case 4:
                    var Offset = BinaryReader.ReadUInt32();
                    // Pointer to string name.
                    if (Offset != 0)
                    {
                        throw (new NotImplementedException());
                    }
            break;
            default:
                IndexName = Name = Stream.ReadStringz(ExtraEntrySizeof);
            break;
         }

            if (n == Header.ListCount - 1)
            {
                // Ignore last element with an empty name.
                if (Name.Length == 0)
                {
                    continue;
                }
            }

            var Entry = new Entry(this, EntryStruct, Name);
            Entry.Index = n;
            Entries.Add(IndexName, Entry);
        }
    }

    public override void Save(Stream Stream)
    {
        var EntryListWithLinks = EntryList.Union(new Entry[] { new Entry(FPS4: this, Stream: new MemoryStream(), Name:"") });
        var EntryListWithoutLinks = EntryListWithLinks.Where(Entry => !Entry.IsLinked);

        var SectorPadding = 0x800;
        var BinaryWriter = new BinaryWriter(Stream);
        Header.Magic = Encoding.ASCII.GetBytes("FPS4");
        Header.ListStart = (uint)Marshal.SizeOf(typeof(HeaderStruct));
        var ExtraEntrySizeof = Header.EntrySizeof - 0x0C;
        var OriginalFilePathBytes = Encoding.GetEncoding("Shift-JIS").GetBytes(OriginalFilePath);
        var DataStartOffset = MathUtils.Align(Header.ListStart + Header.EntrySizeof * EntryListWithLinks.Count() + OriginalFilePathBytes.Length, SectorPadding);

        Stream.WriteStruct(Header);
        long CurrentOffset = DataStartOffset;

        // PASS1. First we calculate all the pointers for non-linked entries.
        foreach (var Entry in EntryListWithoutLinks)
        {
            var LengthReal = Entry.Stream.Length;
            var LengthSectorAligned = (uint)MathUtils.Align(LengthReal, SectorPadding);

            Entry.EntryStruct = new EntryStruct()
            {
                Offset = (uint)CurrentOffset,
                LengthSectorAligned = (uint)LengthSectorAligned,
                LengthReal = (uint)LengthReal,
            };

            CurrentOffset += LengthSectorAligned;
        }

        // PASS2. We then write all the LinkedEntryStructs.
        foreach (var Entry in EntryListWithLinks)
        {
            Stream.WriteStruct(Entry.LinkedEntryStruct);

            switch (ExtraEntrySizeof)
            {
                case 0:
                    break;
                case 4:
                    // Pointer to string name.
                    /*
                    if (Offset != 0)
                    {
                        throw (new NotImplementedException());
                    }
                    //var Offset = BinaryReader.ReadUInt32();
                    */
                    if (Entry.Name == "")
                    {
                        BinaryWriter.Write((uint)0);
                    }
                    else
                    {
                        throw (new NotImplementedException());
                    }
                    break;
                default:
                    Stream.WriteStringz(Entry.Name, ExtraEntrySizeof);
                    break;
            }
        }

        Stream.WriteBytes(OriginalFilePathBytes);
        Stream.WriteZeroToOffset(DataStartOffset);
        foreach (var Entry in EntryListWithoutLinks)
        {
            Entry.Stream.CopyToFast(Stream);
            Stream.WriteZeroToAlign(SectorPadding);
        }

        Stream.Flush();

        //throw new NotImplementedException();
    }

    public void Dispose()
    {
        if (Stream != null)
        {
            Stream.Dispose();
            Stream = null;
        }
    }

    public IEnumerator<FPS4.Entry> GetEnumerator()
    {
        return Entries.Values.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }

    public Entry this [String index]
    {
        get
        {
            return Entries[index];
        }
        set
        {
            Entries[index] = value;
        }
    }

    public Entry this[int index]
    {
        get
        {
            return Entries[index.ToString()];
        }
        set
        {
            Entries[index.ToString()] = value;
        }
    }

    public int Length
    {
        get
        {
            return Entries.Count;
        }
    }
}
Última edición por soywiz el Sab Abr 23, 2011 12:19 pm, editado 1 vez en total
soywiz
Usuario
Usuario
 
Mensajes: 72
Registrado: Jue Ene 18, 2007 12:29 pm
Ubicación: Valencia

Re: Clases de utilidades para .NET 4.0

Notapor soywiz » Sab Abr 23, 2011 12:11 pm

Próximamente:
Definir sistemas virtuales de archivos mediante nodos, montado, escritura y lectura.
soywiz
Usuario
Usuario
 
Mensajes: 72
Registrado: Jue Ene 18, 2007 12:29 pm
Ubicación: Valencia


Volver a Programación

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 1 invitado

cron