// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename       : Xbox.cs
// Author(s)      : Natalia Portillo <claunia@claunia.com>
//
// Component      : Partitioning scheme plugins.
//
// --[ Description ] ----------------------------------------------------------
//
//     Manages Xbox partitions.
//
// --[ License ] --------------------------------------------------------------
//
//     This library is free software; you can redistribute it and/or modify
//     it under the terms of the GNU Lesser General Public License as
//     published by the Free Software Foundation; either version 2.1 of the
//     License, or (at your option) any later version.
//
//     This library is distributed in the hope that it will be useful, but
//     WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//     Lesser General Public License for more details.
//
//     You should have received a copy of the GNU Lesser General Public
//     License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2026 Natalia Portillo
// ****************************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Attributes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Marshal = Aaru.Helpers.Marshal;

namespace Aaru.Partitions;

/// <inheritdoc />
/// <summary>Implements decoding of Xbox partitions</summary>
[SuppressMessage("ReSharper", "UnusedMember.Local")]
public sealed partial class Xbox : IPartition
{
    const uint XBOX_CIGAM                  = 0x46415458;
    const uint XBOX_MAGIC                  = 0x58544146;
    const long MEMORY_UNIT_DATA_OFF        = 0x7FF000;
    const long XBOX360_SECURITY_SECTOR_OFF = 0x2000;
    const long XBOX360_SYSTEM_CACHE_OFF    = 0x80000;
    const long XBOX360_GAME_CACHE_OFF      = 0x8008000;
    const long XBOX368_SYS_EXT_OFF         = 0x10C080000;
    const long XBOX360_SYS_EXT2_OFF        = 0x118EB0000;
    const long XBOX360_COMPAT_OFF          = 0x120EB0000;
    const long XBOX_360DATA_OFF            = 0x130EB0000;
    const long XBOX360_SECURITY_SECTOR_LEN = 0x80000;
    const long XBOX360_SYSTEM_CACHE_LEN    = 0x80000000;
    const long XBOX360_GAME_CACHE_LEN      = 0xA0E30000;
    const long XBOX368_SYS_EXT_LEN         = 0xCE30000;
    const long XBOX360_SYS_EXT2_LEN        = 0x8000000;
    const long XBOX360_COMPAT_LEN          = 0x10000000;
    const uint XBOX360_DEVKIT_MAGIC        = 0x00020000;

#region IPartition Members

    /// <inheritdoc />
    public string Name => Localization.Xbox_Name;

    /// <inheritdoc />
    public Guid Id => new("E3F6FB91-D358-4F22-A550-81E92D50EB78");

    /// <inheritdoc />
    public string Author => Authors.NATALIA_PORTILLO;

    /// <inheritdoc />
    public bool GetInformation(IMediaImage imagePlugin, out List<Partition> partitions, ulong sectorOffset)
    {
        partitions = [];

        // Xbox partitions always start on 0
        if(sectorOffset != 0) return false;

        ErrorNumber errno = imagePlugin.ReadSector(0, false, out byte[] sector, out _);

        if(errno != ErrorNumber.NoError || sector.Length < 512) return false;

        Xbox360DevKitPartitionTable table = Marshal.ByteArrayToStructureBigEndian<Xbox360DevKitPartitionTable>(sector);

        if(table.magic                             == XBOX360_DEVKIT_MAGIC     &&
           table.contentOff   + table.contentLen   <= imagePlugin.Info.Sectors &&
           table.dashboardOff + table.dashboardLen <= imagePlugin.Info.Sectors)
        {
            var contentPart = new Partition
            {
                Description = Localization.Content_volume,
                Size        = (ulong)table.contentLen * imagePlugin.Info.SectorSize,
                Length      = table.contentLen,
                Sequence    = 1,
                Offset      = (ulong)table.contentOff * imagePlugin.Info.SectorSize,
                Start       = table.contentOff,
                Scheme      = Name
            };

            var dashboardPart = new Partition
            {
                Description = Localization.Dashboard_volume,
                Size        = (ulong)table.dashboardLen * imagePlugin.Info.SectorSize,
                Length      = table.dashboardLen,
                Sequence    = 2,
                Offset      = (ulong)table.dashboardOff * imagePlugin.Info.SectorSize,
                Start       = table.dashboardOff,
                Scheme      = Name
            };

            partitions.Add(contentPart);
            partitions.Add(dashboardPart);

            return true;
        }

        uint temp;

        if(imagePlugin.Info.Sectors > (ulong)(MEMORY_UNIT_DATA_OFF / imagePlugin.Info.SectorSize))
        {
            errno = imagePlugin.ReadSector((ulong)(MEMORY_UNIT_DATA_OFF / imagePlugin.Info.SectorSize),
                                           false,
                                           out sector,
                                           out _);

            if(errno == ErrorNumber.NoError)
            {
                temp = BitConverter.ToUInt32(sector, 0);

                if(temp == XBOX_CIGAM)
                {
                    var sysCachePart = new Partition
                    {
                        Description = Localization.System_cache,
                        Size        = MEMORY_UNIT_DATA_OFF,
                        Length      = (ulong)(MEMORY_UNIT_DATA_OFF / imagePlugin.Info.SectorSize),
                        Sequence    = 1,
                        Offset      = 0,
                        Start       = 0,
                        Scheme      = Name
                    };

                    var dataPart = new Partition
                    {
                        Description = Localization.Data_volume,
                        Size        = imagePlugin.Info.Sectors * imagePlugin.Info.SectorSize - MEMORY_UNIT_DATA_OFF,
                        Length      = imagePlugin.Info.Sectors                               - sysCachePart.Length,
                        Sequence    = 2,
                        Offset      = MEMORY_UNIT_DATA_OFF,
                        Start       = sysCachePart.Length,
                        Scheme      = Name
                    };

                    partitions.Add(sysCachePart);
                    partitions.Add(dataPart);

                    return true;
                }
            }
        }

        if(imagePlugin.Info.Sectors <= (ulong)(XBOX_360DATA_OFF / imagePlugin.Info.SectorSize)) return false;

        {
            errno = imagePlugin.ReadSector((ulong)(XBOX_360DATA_OFF / imagePlugin.Info.SectorSize),
                                           false,
                                           out sector,
                                           out _);

            if(errno != ErrorNumber.NoError) return false;

            temp = BitConverter.ToUInt32(sector, 0);

            if(temp != XBOX_CIGAM) return false;

            var securityPart = new Partition
            {
                Description = Localization.Security_sectors,
                Size        = XBOX360_SECURITY_SECTOR_LEN,
                Length      = (ulong)(XBOX360_SECURITY_SECTOR_LEN / imagePlugin.Info.SectorSize),
                Sequence    = 1,
                Offset      = XBOX360_SECURITY_SECTOR_OFF,
                Start       = (ulong)(XBOX360_SECURITY_SECTOR_OFF / imagePlugin.Info.SectorSize),
                Scheme      = Name
            };

            var sysCachePart = new Partition
            {
                Description = Localization.System_cache,
                Size        = XBOX360_SYSTEM_CACHE_LEN,
                Length      = (ulong)(XBOX360_SYSTEM_CACHE_LEN / imagePlugin.Info.SectorSize),
                Sequence    = 2,
                Offset      = XBOX360_SYSTEM_CACHE_OFF,
                Start       = (ulong)(XBOX360_SYSTEM_CACHE_OFF / imagePlugin.Info.SectorSize),
                Scheme      = Name
            };

            var gameCachePart = new Partition
            {
                Description = Localization.Game_cache,
                Size        = XBOX360_GAME_CACHE_LEN,
                Length      = (ulong)(XBOX360_GAME_CACHE_LEN / imagePlugin.Info.SectorSize),
                Sequence    = 3,
                Offset      = XBOX360_GAME_CACHE_OFF,
                Start       = (ulong)(XBOX360_GAME_CACHE_OFF / imagePlugin.Info.SectorSize),
                Scheme      = Name
            };

            var sysExtPart = new Partition
            {
                Description = Localization.System_volume,
                Size        = XBOX368_SYS_EXT_LEN,
                Length      = (ulong)(XBOX368_SYS_EXT_LEN / imagePlugin.Info.SectorSize),
                Sequence    = 4,
                Offset      = XBOX368_SYS_EXT_OFF,
                Start       = (ulong)(XBOX368_SYS_EXT_OFF / imagePlugin.Info.SectorSize),
                Scheme      = Name
            };

            var sysExt2Part = new Partition
            {
                Description = Localization.System_volume_2,
                Size        = XBOX360_SYS_EXT2_LEN,
                Length      = (ulong)(XBOX360_SYS_EXT2_LEN / imagePlugin.Info.SectorSize),
                Sequence    = 5,
                Offset      = XBOX360_SYS_EXT2_OFF,
                Start       = (ulong)(XBOX360_SYS_EXT2_OFF / imagePlugin.Info.SectorSize),
                Scheme      = Name
            };

            var xbox1Part = new Partition
            {
                Description = Localization.Xbox_backwards_compatibility,
                Size        = XBOX360_COMPAT_LEN,
                Length      = (ulong)(XBOX360_COMPAT_LEN / imagePlugin.Info.SectorSize),
                Sequence    = 6,
                Offset      = XBOX360_COMPAT_OFF,
                Start       = (ulong)(XBOX360_COMPAT_OFF / imagePlugin.Info.SectorSize),
                Scheme      = Name
            };

            var dataPart = new Partition
            {
                Description = Localization.Data_volume,
                Sequence    = 7,
                Offset      = XBOX_360DATA_OFF,
                Start       = (ulong)(XBOX_360DATA_OFF / imagePlugin.Info.SectorSize),
                Scheme      = Name
            };

            dataPart.Length = imagePlugin.Info.Sectors - dataPart.Start;
            dataPart.Size   = dataPart.Length * imagePlugin.Info.SectorSize;

            partitions.Add(securityPart);
            partitions.Add(sysCachePart);
            partitions.Add(gameCachePart);
            partitions.Add(sysExtPart);
            partitions.Add(sysExt2Part);
            partitions.Add(xbox1Part);
            partitions.Add(dataPart);

            return true;
        }
    }

#endregion

#region Nested type: Xbox360DevKitPartitionTable

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    [SwapEndian]
    partial struct Xbox360DevKitPartitionTable
    {
        public uint magic;
        public uint unknown;
        public uint contentOff;
        public uint contentLen;
        public uint dashboardOff;
        public uint dashboardLen;
    }

#endregion
}