mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Update layout of Loader and Kvdb structs
This commit is contained in:
parent
b0e679d000
commit
e1fd31c1ff
31
src/LibHac/Common/FixedArrays/Array48.cs
Normal file
31
src/LibHac/Common/FixedArrays/Array48.cs
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma warning disable CS0169, IDE0051 // Remove unused private members
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Common.FixedArrays;
|
||||
|
||||
public struct Array48<T>
|
||||
{
|
||||
public const int Length = 48;
|
||||
|
||||
private Array32<T> _0;
|
||||
private Array16<T> _32;
|
||||
|
||||
public ref T this[int i] => ref Items[i];
|
||||
|
||||
public Span<T> Items
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length);
|
||||
}
|
||||
|
||||
public readonly ReadOnlySpan<T> ItemsRo
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ReadOnlySpan<T>(in Array48<T> value) => value.ItemsRo;
|
||||
}
|
@ -1,15 +1,13 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
|
||||
namespace LibHac.Kvdb;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0xC)]
|
||||
public struct KeyValueArchiveHeader
|
||||
{
|
||||
public const uint ExpectedMagic = 0x564B4D49; // IMKV
|
||||
public static readonly uint ExpectedMagic = 0x564B4D49; // IMKV
|
||||
|
||||
public uint Magic;
|
||||
public int Reserved;
|
||||
@ -25,10 +23,9 @@ public struct KeyValueArchiveHeader
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0xC)]
|
||||
internal struct KeyValueArchiveEntryHeader
|
||||
{
|
||||
public const uint ExpectedMagic = 0x4E454D49; // IMEN
|
||||
public static readonly uint ExpectedMagic = 0x4E454D49; // IMEN
|
||||
|
||||
public uint Magic;
|
||||
public int KeySize;
|
||||
@ -202,4 +199,4 @@ internal ref struct KeyValueArchiveBufferWriter
|
||||
Write(key);
|
||||
Write(value);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,65 +2,63 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Common.FixedArrays;
|
||||
|
||||
namespace LibHac.Loader;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x100)]
|
||||
public struct NsoHeader
|
||||
{
|
||||
public const int SegmentCount = 3;
|
||||
public static readonly int SegmentCount = 3;
|
||||
|
||||
[FieldOffset(0x00)] public uint Magic;
|
||||
[FieldOffset(0x04)] public uint Version;
|
||||
[FieldOffset(0x08)] public uint Reserved08;
|
||||
[FieldOffset(0x0C)] public Flag Flags;
|
||||
public uint Magic;
|
||||
public uint Version;
|
||||
public uint Reserved08;
|
||||
public Flag Flags;
|
||||
|
||||
[FieldOffset(0x10)] public uint TextFileOffset;
|
||||
[FieldOffset(0x14)] public uint TextMemoryOffset;
|
||||
[FieldOffset(0x18)] public uint TextSize;
|
||||
public uint TextFileOffset;
|
||||
public uint TextMemoryOffset;
|
||||
public uint TextSize;
|
||||
|
||||
[FieldOffset(0x1C)] public uint ModuleNameOffset;
|
||||
public uint ModuleNameOffset;
|
||||
|
||||
[FieldOffset(0x20)] public uint RoFileOffset;
|
||||
[FieldOffset(0x24)] public uint RoMemoryOffset;
|
||||
[FieldOffset(0x28)] public uint RoSize;
|
||||
public uint RoFileOffset;
|
||||
public uint RoMemoryOffset;
|
||||
public uint RoSize;
|
||||
|
||||
[FieldOffset(0x2C)] public uint ModuleNameSize;
|
||||
public uint ModuleNameSize;
|
||||
|
||||
[FieldOffset(0x30)] public uint DataFileOffset;
|
||||
[FieldOffset(0x34)] public uint DataMemoryOffset;
|
||||
[FieldOffset(0x38)] public uint DataSize;
|
||||
public uint DataFileOffset;
|
||||
public uint DataMemoryOffset;
|
||||
public uint DataSize;
|
||||
|
||||
[FieldOffset(0x3C)] public uint BssSize;
|
||||
public uint BssSize;
|
||||
|
||||
[FieldOffset(0x40)] public Buffer32 ModuleId;
|
||||
public Array32<byte> ModuleId;
|
||||
|
||||
// Size of the sections in the NSO file
|
||||
[FieldOffset(0x60)] public uint TextFileSize;
|
||||
[FieldOffset(0x64)] public uint RoFileSize;
|
||||
[FieldOffset(0x68)] public uint DataFileSize;
|
||||
public uint TextFileSize;
|
||||
public uint RoFileSize;
|
||||
public uint DataFileSize;
|
||||
|
||||
[FieldOffset(0x6C)] private byte _reserved6C;
|
||||
public Array28<byte> Reserved6C;
|
||||
|
||||
[FieldOffset(0x88)] public uint ApiInfoOffset;
|
||||
[FieldOffset(0x8C)] public uint ApiInfoSize;
|
||||
[FieldOffset(0x90)] public uint DynStrOffset;
|
||||
[FieldOffset(0x94)] public uint DynStrSize;
|
||||
[FieldOffset(0x98)] public uint DynSymOffset;
|
||||
[FieldOffset(0x9C)] public uint DynSymSize;
|
||||
public uint ApiInfoOffset;
|
||||
public uint ApiInfoSize;
|
||||
public uint DynStrOffset;
|
||||
public uint DynStrSize;
|
||||
public uint DynSymOffset;
|
||||
public uint DynSymSize;
|
||||
|
||||
[FieldOffset(0xA0)] public Buffer32 TextHash;
|
||||
[FieldOffset(0xC0)] public Buffer32 RoHash;
|
||||
[FieldOffset(0xE0)] public Buffer32 DataHash;
|
||||
public Array32<byte> TextHash;
|
||||
public Array32<byte> RoHash;
|
||||
public Array32<byte> DataHash;
|
||||
|
||||
public Span<SegmentHeader> Segments =>
|
||||
SpanHelpers.CreateSpan(ref Unsafe.As<uint, SegmentHeader>(ref TextFileOffset), SegmentCount);
|
||||
|
||||
public Span<uint> CompressedSizes => SpanHelpers.CreateSpan(ref TextFileSize, SegmentCount);
|
||||
|
||||
public Span<Buffer32> SegmentHashes => SpanHelpers.CreateSpan(ref TextHash, SegmentCount);
|
||||
|
||||
public Span<byte> Reserved6C => SpanHelpers.CreateSpan(ref _reserved6C, 0x1C);
|
||||
public Span<Array32<byte>> SegmentHashes => SpanHelpers.CreateSpan(ref TextHash, SegmentCount);
|
||||
|
||||
[Flags]
|
||||
public enum Flag
|
||||
@ -73,11 +71,12 @@ public struct NsoHeader
|
||||
DataHash = 1 << 5
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SegmentHeader
|
||||
{
|
||||
public uint FileOffset;
|
||||
public uint MemoryOffset;
|
||||
public uint Size;
|
||||
private int _unused;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Common.FixedArrays;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Util;
|
||||
@ -56,7 +57,7 @@ public class NsoReader
|
||||
Header.SegmentHashes[(int)segment], isCompressed, checkHash, buffer);
|
||||
}
|
||||
|
||||
private Result ReadSegmentImpl(ref NsoHeader.SegmentHeader segment, uint fileSize, Buffer32 fileHash,
|
||||
private Result ReadSegmentImpl(ref NsoHeader.SegmentHeader segment, uint fileSize, Array32<byte> fileHash,
|
||||
bool isCompressed, bool checkHash, Span<byte> buffer)
|
||||
{
|
||||
// Select read size based on compression.
|
||||
@ -90,10 +91,10 @@ public class NsoReader
|
||||
// Check hash if necessary.
|
||||
if (checkHash)
|
||||
{
|
||||
var hash = new Buffer32();
|
||||
Crypto.Sha256.GenerateSha256Hash(buffer.Slice(0, (int)segment.Size), hash.Bytes);
|
||||
var hash = new Array32<byte>();
|
||||
Crypto.Sha256.GenerateSha256Hash(buffer.Slice(0, (int)segment.Size), hash.Items);
|
||||
|
||||
if (hash.Bytes.SequenceCompareTo(fileHash.Bytes) != 0)
|
||||
if (hash.ItemsRo.SequenceCompareTo(fileHash) != 0)
|
||||
return ResultLoader.InvalidNso.Log();
|
||||
}
|
||||
|
||||
|
@ -27,26 +27,22 @@ public struct Meta
|
||||
|
||||
public uint Magic;
|
||||
public int SignatureKeyGeneration;
|
||||
private Array4<byte> _reserved08;
|
||||
public Array4<byte> Reserved08;
|
||||
public byte Flags;
|
||||
private byte _reserved0D;
|
||||
public byte Reserved0D;
|
||||
public byte MainThreadPriority;
|
||||
public byte DefaultCpuId;
|
||||
private Array4<byte> _reserved10;
|
||||
public Array4<byte> Reserved10;
|
||||
public uint SystemResourceSize;
|
||||
public uint Version;
|
||||
public uint MainThreadStackSize;
|
||||
private Array16<byte> _programName;
|
||||
private Array16<byte> _productCode;
|
||||
private Array32<byte> _reserved40;
|
||||
private Array16<byte> _reserved60;
|
||||
public Array16<byte> ProgramName;
|
||||
public Array16<byte> ProductCode;
|
||||
public Array48<byte> Reserved40;
|
||||
public int AciOffset;
|
||||
public int AciSize;
|
||||
public int AcidOffset;
|
||||
public int AcidSize;
|
||||
|
||||
public readonly ReadOnlySpan<byte> ProgramName => _programName.ItemsRo;
|
||||
public readonly ReadOnlySpan<byte> ProductCode => _productCode.ItemsRo;
|
||||
}
|
||||
|
||||
public struct AciHeader
|
||||
@ -54,24 +50,24 @@ public struct AciHeader
|
||||
public static readonly uint MagicValue = 0x30494341; // ACI0
|
||||
|
||||
public uint Magic;
|
||||
private Array12<byte> _reserved04;
|
||||
public Array12<byte> Reserved04;
|
||||
public ProgramId ProgramId;
|
||||
private Array8<byte> _reserved18;
|
||||
public Array8<byte> Reserved18;
|
||||
public int FsAccessControlOffset;
|
||||
public int FsAccessControlSize;
|
||||
public int ServiceAccessControlOffset;
|
||||
public int ServiceAccessControlSize;
|
||||
public int KernelCapabilityOffset;
|
||||
public int KernelCapabilitySize;
|
||||
private Array4<byte> _reserved38;
|
||||
public Array8<byte> Reserved38;
|
||||
}
|
||||
|
||||
public struct AcidHeaderData
|
||||
{
|
||||
public static readonly uint MagicValue = 0x44494341; // ACID
|
||||
|
||||
private Array256<byte> _signature;
|
||||
private Array256<byte> _modulus;
|
||||
public Array256<byte> Signature;
|
||||
public Array256<byte> Modulus;
|
||||
public uint Magic;
|
||||
public int Size;
|
||||
public byte Version;
|
||||
@ -84,8 +80,5 @@ public struct AcidHeaderData
|
||||
public int ServiceAccessControlSize;
|
||||
public int KernelCapabilityOffset;
|
||||
public int KernelCapabilitySize;
|
||||
private Array4<byte> _reserved238;
|
||||
|
||||
public readonly ReadOnlySpan<byte> Signature => _signature.ItemsRo;
|
||||
public readonly ReadOnlySpan<byte> Modulus => _modulus.ItemsRo;
|
||||
}
|
||||
public Array4<byte> Reserved238;
|
||||
}
|
33
tests/LibHac.Tests/Kvdb/TypeLayoutTests.cs
Normal file
33
tests/LibHac.Tests/Kvdb/TypeLayoutTests.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Kvdb;
|
||||
using Xunit;
|
||||
using static LibHac.Tests.Common.Layout;
|
||||
|
||||
namespace LibHac.Tests.Kvdb;
|
||||
|
||||
public class TypeLayoutTests
|
||||
{
|
||||
[Fact]
|
||||
public static void KeyValueArchiveHeader_Layout()
|
||||
{
|
||||
var s = new KeyValueArchiveHeader();
|
||||
|
||||
Assert.Equal(0xC, Unsafe.SizeOf<KeyValueArchiveHeader>());
|
||||
|
||||
Assert.Equal(0x0, GetOffset(in s, in s.Magic));
|
||||
Assert.Equal(0x4, GetOffset(in s, in s.Reserved));
|
||||
Assert.Equal(0x8, GetOffset(in s, in s.EntryCount));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void KeyValueArchiveEntryHeader_Layout()
|
||||
{
|
||||
var s = new KeyValueArchiveEntryHeader();
|
||||
|
||||
Assert.Equal(0xC, Unsafe.SizeOf<KeyValueArchiveEntryHeader>());
|
||||
|
||||
Assert.Equal(0x0, GetOffset(in s, in s.Magic));
|
||||
Assert.Equal(0x4, GetOffset(in s, in s.KeySize));
|
||||
Assert.Equal(0x8, GetOffset(in s, in s.ValueSize));
|
||||
}
|
||||
}
|
130
tests/LibHac.Tests/Loader/TypeLayoutTests.cs
Normal file
130
tests/LibHac.Tests/Loader/TypeLayoutTests.cs
Normal file
@ -0,0 +1,130 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Loader;
|
||||
using Xunit;
|
||||
using static LibHac.Tests.Common.Layout;
|
||||
|
||||
namespace LibHac.Tests.Loader;
|
||||
|
||||
public class TypeLayoutTests
|
||||
{
|
||||
[Fact]
|
||||
public static void NsoHeader_Layout()
|
||||
{
|
||||
var s = new NsoHeader();
|
||||
|
||||
Assert.Equal(0x100, Unsafe.SizeOf<NsoHeader>());
|
||||
|
||||
Assert.Equal(0x00, GetOffset(in s, in s.Magic));
|
||||
Assert.Equal(0x04, GetOffset(in s, in s.Version));
|
||||
Assert.Equal(0x08, GetOffset(in s, in s.Reserved08));
|
||||
Assert.Equal(0x0C, GetOffset(in s, in s.Flags));
|
||||
|
||||
Assert.Equal(0x10, GetOffset(in s, in s.TextFileOffset));
|
||||
Assert.Equal(0x14, GetOffset(in s, in s.TextMemoryOffset));
|
||||
Assert.Equal(0x18, GetOffset(in s, in s.TextSize));
|
||||
|
||||
Assert.Equal(0x1C, GetOffset(in s, in s.ModuleNameOffset));
|
||||
|
||||
Assert.Equal(0x20, GetOffset(in s, in s.RoFileOffset));
|
||||
Assert.Equal(0x24, GetOffset(in s, in s.RoMemoryOffset));
|
||||
Assert.Equal(0x28, GetOffset(in s, in s.RoSize));
|
||||
|
||||
Assert.Equal(0x2C, GetOffset(in s, in s.ModuleNameSize));
|
||||
|
||||
Assert.Equal(0x30, GetOffset(in s, in s.DataFileOffset));
|
||||
Assert.Equal(0x34, GetOffset(in s, in s.DataMemoryOffset));
|
||||
Assert.Equal(0x38, GetOffset(in s, in s.DataSize));
|
||||
|
||||
Assert.Equal(0x3C, GetOffset(in s, in s.BssSize));
|
||||
|
||||
Assert.Equal(0x40, GetOffset(in s, in s.ModuleId));
|
||||
|
||||
Assert.Equal(0x60, GetOffset(in s, in s.TextFileSize));
|
||||
Assert.Equal(0x64, GetOffset(in s, in s.RoFileSize));
|
||||
Assert.Equal(0x68, GetOffset(in s, in s.DataFileSize));
|
||||
|
||||
Assert.Equal(0x6C, GetOffset(in s, in s.Reserved6C));
|
||||
|
||||
Assert.Equal(0x88, GetOffset(in s, in s.ApiInfoOffset));
|
||||
Assert.Equal(0x8C, GetOffset(in s, in s.ApiInfoSize));
|
||||
Assert.Equal(0x90, GetOffset(in s, in s.DynStrOffset));
|
||||
Assert.Equal(0x94, GetOffset(in s, in s.DynStrSize));
|
||||
Assert.Equal(0x98, GetOffset(in s, in s.DynSymOffset));
|
||||
Assert.Equal(0x9C, GetOffset(in s, in s.DynSymSize));
|
||||
|
||||
Assert.Equal(0xA0, GetOffset(in s, in s.TextHash));
|
||||
Assert.Equal(0xC0, GetOffset(in s, in s.RoHash));
|
||||
Assert.Equal(0xE0, GetOffset(in s, in s.DataHash));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void Meta_layout()
|
||||
{
|
||||
var s = new Meta();
|
||||
|
||||
Assert.Equal(0x80, Unsafe.SizeOf<Meta>());
|
||||
|
||||
Assert.Equal(0x00, GetOffset(in s, in s.Magic));
|
||||
Assert.Equal(0x04, GetOffset(in s, in s.SignatureKeyGeneration));
|
||||
Assert.Equal(0x08, GetOffset(in s, in s.Reserved08));
|
||||
Assert.Equal(0x0C, GetOffset(in s, in s.Flags));
|
||||
Assert.Equal(0x0D, GetOffset(in s, in s.Reserved0D));
|
||||
Assert.Equal(0x0E, GetOffset(in s, in s.MainThreadPriority));
|
||||
Assert.Equal(0x0F, GetOffset(in s, in s.DefaultCpuId));
|
||||
Assert.Equal(0x10, GetOffset(in s, in s.Reserved10));
|
||||
Assert.Equal(0x14, GetOffset(in s, in s.SystemResourceSize));
|
||||
Assert.Equal(0x18, GetOffset(in s, in s.Version));
|
||||
Assert.Equal(0x1C, GetOffset(in s, in s.MainThreadStackSize));
|
||||
Assert.Equal(0x20, GetOffset(in s, in s.ProgramName));
|
||||
Assert.Equal(0x30, GetOffset(in s, in s.ProductCode));
|
||||
Assert.Equal(0x40, GetOffset(in s, in s.Reserved40));
|
||||
Assert.Equal(0x70, GetOffset(in s, in s.AciOffset));
|
||||
Assert.Equal(0x74, GetOffset(in s, in s.AciSize));
|
||||
Assert.Equal(0x78, GetOffset(in s, in s.AcidOffset));
|
||||
Assert.Equal(0x7C, GetOffset(in s, in s.AcidSize));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void AciHeader_Layout()
|
||||
{
|
||||
var s = new AciHeader();
|
||||
|
||||
Assert.Equal(0x40, Unsafe.SizeOf<AciHeader>());
|
||||
|
||||
Assert.Equal(0x00, GetOffset(in s, in s.Magic));
|
||||
Assert.Equal(0x04, GetOffset(in s, in s.Reserved04));
|
||||
Assert.Equal(0x10, GetOffset(in s, in s.ProgramId));
|
||||
Assert.Equal(0x18, GetOffset(in s, in s.Reserved18));
|
||||
Assert.Equal(0x20, GetOffset(in s, in s.FsAccessControlOffset));
|
||||
Assert.Equal(0x24, GetOffset(in s, in s.FsAccessControlSize));
|
||||
Assert.Equal(0x28, GetOffset(in s, in s.ServiceAccessControlOffset));
|
||||
Assert.Equal(0x2C, GetOffset(in s, in s.ServiceAccessControlSize));
|
||||
Assert.Equal(0x30, GetOffset(in s, in s.KernelCapabilityOffset));
|
||||
Assert.Equal(0x34, GetOffset(in s, in s.KernelCapabilitySize));
|
||||
Assert.Equal(0x38, GetOffset(in s, in s.Reserved38));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void AcidHeaderData_Layout()
|
||||
{
|
||||
var s = new AcidHeaderData();
|
||||
|
||||
Assert.Equal(0x240, Unsafe.SizeOf<AcidHeaderData>());
|
||||
|
||||
Assert.Equal(0x000, GetOffset(in s, in s.Signature));
|
||||
Assert.Equal(0x100, GetOffset(in s, in s.Modulus));
|
||||
Assert.Equal(0x200, GetOffset(in s, in s.Magic));
|
||||
Assert.Equal(0x204, GetOffset(in s, in s.Size));
|
||||
Assert.Equal(0x208, GetOffset(in s, in s.Version));
|
||||
Assert.Equal(0x20C, GetOffset(in s, in s.Flags));
|
||||
Assert.Equal(0x210, GetOffset(in s, in s.ProgramIdMin));
|
||||
Assert.Equal(0x218, GetOffset(in s, in s.ProgramIdMax));
|
||||
Assert.Equal(0x220, GetOffset(in s, in s.FsAccessControlOffset));
|
||||
Assert.Equal(0x224, GetOffset(in s, in s.FsAccessControlSize));
|
||||
Assert.Equal(0x228, GetOffset(in s, in s.ServiceAccessControlOffset));
|
||||
Assert.Equal(0x22C, GetOffset(in s, in s.ServiceAccessControlSize));
|
||||
Assert.Equal(0x230, GetOffset(in s, in s.KernelCapabilityOffset));
|
||||
Assert.Equal(0x234, GetOffset(in s, in s.KernelCapabilitySize));
|
||||
Assert.Equal(0x238, GetOffset(in s, in s.Reserved238));
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Loader;
|
||||
using Xunit;
|
||||
|
||||
namespace LibHac.Tests.Loader;
|
||||
|
||||
public class TypeSizeTests
|
||||
{
|
||||
[Fact]
|
||||
public static void MetaSizeIsCorrect()
|
||||
{
|
||||
Assert.Equal(0x80, Unsafe.SizeOf<Meta>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void AciSizeIsCorrect()
|
||||
{
|
||||
Assert.Equal(0x40, Unsafe.SizeOf<AciHeader>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void AcidSizeIsCorrect()
|
||||
{
|
||||
Assert.Equal(0x240, Unsafe.SizeOf<AcidHeaderData>());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user