mirror of
https://github.com/zeldaret/botw
synced 2026-07-03 12:10:14 -04:00
Switch to subrepos
git subrepo clone https://github.com/open-ead/sead lib/sead subrepo: subdir: "lib/sead" merged: "1b66e825d" upstream: origin: "https://github.com/open-ead/sead" branch: "master" commit: "1b66e825d" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo" commit: "2f68596" git subrepo clone (merge) https://github.com/open-ead/nnheaders lib/NintendoSDK subrepo: subdir: "lib/NintendoSDK" merged: "9ee21399f" upstream: origin: "https://github.com/open-ead/nnheaders" branch: "master" commit: "9ee21399f" git-subrepo: version: "0.4.3" origin: "ssh://git@github.com/ingydotnet/git-subrepo" commit: "2f68596" git subrepo clone https://github.com/open-ead/agl lib/agl subrepo: subdir: "lib/agl" merged: "7c063271b" upstream: origin: "https://github.com/open-ead/agl" branch: "master" commit: "7c063271b" git-subrepo: version: "0.4.3" origin: "ssh://git@github.com/ingydotnet/git-subrepo" commit: "2f68596" git subrepo clone https://github.com/open-ead/EventFlow lib/EventFlow subrepo: subdir: "lib/EventFlow" merged: "c35d21b34" upstream: origin: "https://github.com/open-ead/EventFlow" branch: "master" commit: "c35d21b34" git-subrepo: version: "0.4.3" origin: "ssh://git@github.com/ingydotnet/git-subrepo" commit: "2f68596"
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
#include <cstdint>
|
||||
#include <ore/BinaryFile.h>
|
||||
#include <ore/BitUtils.h>
|
||||
|
||||
namespace ore {
|
||||
|
||||
bool BinaryFileHeader::IsValid(s64 magic_, int ver_major_, int ver_minor_, int ver_patch_,
|
||||
int ver_sub_) const {
|
||||
bool valid = true;
|
||||
valid &= int(ver_major) == ver_major_ && int(ver_minor) == ver_minor_ &&
|
||||
magic == magic_ & int(ver_patch) <= ver_patch_;
|
||||
valid &= IsEndianReverse() || IsEndianValid();
|
||||
valid &= IsAlignmentValid();
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool BinaryFileHeader::IsSignatureValid(s64 magic_) const {
|
||||
return magic == magic_;
|
||||
}
|
||||
|
||||
bool BinaryFileHeader::IsVersionValid(int major, int minor, int patch, int sub) const {
|
||||
if (int(ver_major) != major)
|
||||
return false;
|
||||
if (int(ver_minor) != minor)
|
||||
return false;
|
||||
if (int(ver_patch) > patch)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BinaryFileHeader::IsEndianReverse() const {
|
||||
return bom == s16(0xFFFE);
|
||||
}
|
||||
|
||||
bool BinaryFileHeader::IsEndianValid() const {
|
||||
return bom == s16(0xFEFF);
|
||||
}
|
||||
|
||||
bool BinaryFileHeader::IsAlignmentValid() const {
|
||||
return (std::uintptr_t(this) & (GetAlignment() - 1)) == 0;
|
||||
}
|
||||
|
||||
int BinaryFileHeader::GetAlignment() const {
|
||||
return 1 << alignment;
|
||||
}
|
||||
|
||||
static constexpr u32 FlagRelocated = 1 << 0;
|
||||
|
||||
bool BinaryFileHeader::IsRelocated() const {
|
||||
return relocation_flags & FlagRelocated;
|
||||
}
|
||||
|
||||
void BinaryFileHeader::SetRelocated(bool relocated) {
|
||||
if (relocated)
|
||||
relocation_flags |= FlagRelocated;
|
||||
else
|
||||
relocation_flags &= ~FlagRelocated;
|
||||
}
|
||||
|
||||
void BinaryFileHeader::SetByteOrderMark() {
|
||||
bom = s16(0xFEFF);
|
||||
}
|
||||
|
||||
int BinaryFileHeader::GetFileSize() const {
|
||||
return file_size;
|
||||
}
|
||||
|
||||
void BinaryFileHeader::SetFileSize(int size) {
|
||||
file_size = size;
|
||||
}
|
||||
|
||||
void BinaryFileHeader::SetAlignment(int alignment_) {
|
||||
alignment = CountTrailingZeros(u32(alignment_));
|
||||
}
|
||||
|
||||
StringView BinaryFileHeader::GetFileName() const {
|
||||
StringView name;
|
||||
if (file_name_offset != 0)
|
||||
name = reinterpret_cast<const char*>(this) + file_name_offset;
|
||||
return name;
|
||||
}
|
||||
|
||||
void BinaryFileHeader::SetFileName(const StringView& name) {
|
||||
if (name.empty()) {
|
||||
file_name_offset = 0;
|
||||
} else {
|
||||
file_name_offset = int(intptr_t(name.data()) - intptr_t(this));
|
||||
#ifdef MATCHING_HACK_NX_CLANG
|
||||
asm("");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
RelocationTable* BinaryFileHeader::GetRelocationTable() {
|
||||
if (relocation_table_offset == 0)
|
||||
return nullptr;
|
||||
return reinterpret_cast<RelocationTable*>(reinterpret_cast<char*>(this) +
|
||||
relocation_table_offset);
|
||||
}
|
||||
|
||||
void BinaryFileHeader::SetRelocationTable(RelocationTable* table) {
|
||||
if (table == nullptr) {
|
||||
relocation_table_offset = 0;
|
||||
} else {
|
||||
relocation_table_offset = int(intptr_t(table) - intptr_t(this));
|
||||
#ifdef MATCHING_HACK_NX_CLANG
|
||||
asm("");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
BinaryBlockHeader* BinaryFileHeader::GetFirstBlock() {
|
||||
if (first_block_offset == 0)
|
||||
return nullptr;
|
||||
return reinterpret_cast<BinaryBlockHeader*>(reinterpret_cast<char*>(this) + first_block_offset);
|
||||
}
|
||||
|
||||
const BinaryBlockHeader* BinaryFileHeader::GetFirstBlock() const {
|
||||
if (first_block_offset == 0)
|
||||
return nullptr;
|
||||
return reinterpret_cast<const BinaryBlockHeader*>(reinterpret_cast<const char*>(this) +
|
||||
first_block_offset);
|
||||
}
|
||||
|
||||
BinaryBlockHeader* BinaryFileHeader::FindFirstBlock(int type) {
|
||||
auto* block = GetFirstBlock();
|
||||
if (!block || block->magic == type)
|
||||
return block;
|
||||
return block->FindNextBlock(type);
|
||||
}
|
||||
|
||||
const BinaryBlockHeader* BinaryFileHeader::FindFirstBlock(int type) const {
|
||||
auto* block = GetFirstBlock();
|
||||
if (!block || block->magic == type)
|
||||
return block;
|
||||
return block->FindNextBlock(type);
|
||||
}
|
||||
|
||||
void BinaryFileHeader::SetFirstBlock(BinaryBlockHeader* block) {
|
||||
if (block == nullptr)
|
||||
first_block_offset = 0;
|
||||
else
|
||||
first_block_offset = int(intptr_t(block) - intptr_t(this));
|
||||
}
|
||||
|
||||
BinaryBlockHeader* BinaryBlockHeader::FindNextBlock(int type) {
|
||||
auto* block = this;
|
||||
do
|
||||
block = block->GetNextBlock();
|
||||
while (block && block->magic != type);
|
||||
return block;
|
||||
}
|
||||
|
||||
const BinaryBlockHeader* BinaryBlockHeader::FindNextBlock(int type) const {
|
||||
auto* block = this;
|
||||
do
|
||||
block = block->GetNextBlock();
|
||||
while (block && block->magic != type);
|
||||
return block;
|
||||
}
|
||||
|
||||
BinaryBlockHeader* BinaryBlockHeader::GetNextBlock() {
|
||||
if (next_block_offset == 0)
|
||||
return nullptr;
|
||||
return reinterpret_cast<BinaryBlockHeader*>(reinterpret_cast<char*>(this) + next_block_offset);
|
||||
}
|
||||
|
||||
const BinaryBlockHeader* BinaryBlockHeader::GetNextBlock() const {
|
||||
if (next_block_offset == 0)
|
||||
return nullptr;
|
||||
return reinterpret_cast<const BinaryBlockHeader*>(reinterpret_cast<const char*>(this) +
|
||||
next_block_offset);
|
||||
}
|
||||
|
||||
void BinaryBlockHeader::SetNextBlock(BinaryBlockHeader* block) {
|
||||
if (block == nullptr)
|
||||
next_block_offset = 0;
|
||||
else
|
||||
next_block_offset = int(intptr_t(block) - intptr_t(this));
|
||||
}
|
||||
|
||||
} // namespace ore
|
||||
@@ -0,0 +1,115 @@
|
||||
#include <ore/BitUtils.h>
|
||||
|
||||
namespace ore {
|
||||
|
||||
void BitArray::SetAllOn() {
|
||||
const int num = m_num_bits >> ShiftAmount;
|
||||
Fill(num, Word(-1));
|
||||
|
||||
u32 remainder = u32(m_num_bits) % NumBitsPerWord;
|
||||
if (remainder != 0)
|
||||
m_words[num] = (1ul << remainder) - 1;
|
||||
}
|
||||
|
||||
void BitArray::SetAllOff() {
|
||||
Fill(GetNumWords(), Word(0));
|
||||
}
|
||||
|
||||
BitArray::TestIter BitArray::BeginTest() const {
|
||||
return TestIter(m_words, m_words + GetNumWords());
|
||||
}
|
||||
|
||||
BitArray::TestIter BitArray::EndTest() const {
|
||||
return TestIter(nullptr, nullptr);
|
||||
}
|
||||
|
||||
BitArray::TestClearIter BitArray::BeginTestClear() {
|
||||
return TestClearIter(m_words, m_words + GetNumWords());
|
||||
}
|
||||
|
||||
BitArray::TestClearIter BitArray::EndTestClear() {
|
||||
return TestClearIter(nullptr, nullptr);
|
||||
}
|
||||
|
||||
BitArray::TestIter::TestIter(const BitArray::Word* start, const BitArray::Word* end) {
|
||||
for (auto* it = start; it != end; ++it) {
|
||||
if (*it != 0) {
|
||||
auto idx = CountTrailingZeros(*it);
|
||||
idx += 8 * int(intptr_t(it) - intptr_t(start)) & ClearMask;
|
||||
m_bit = idx;
|
||||
m_current_word = it;
|
||||
m_last_word = end;
|
||||
m_next = *it & (*it - 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SetInvalid();
|
||||
}
|
||||
|
||||
BitArray::TestIter& BitArray::TestIter::operator++() {
|
||||
m_bit &= ClearMask;
|
||||
|
||||
// Fast path: we still have bits in the current word
|
||||
if (m_next != 0) {
|
||||
m_bit += CountTrailingZeros(m_next);
|
||||
m_next &= m_next - 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Find the next nonzero word and the first set bit in it
|
||||
++m_current_word;
|
||||
for (; m_current_word != m_last_word; ++m_current_word) {
|
||||
m_bit += NumBitsPerWord;
|
||||
if (*m_current_word == 0)
|
||||
continue;
|
||||
m_bit += CountTrailingZeros(*m_current_word);
|
||||
m_next = *m_current_word & (*m_current_word - 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SetInvalid();
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitArray::TestClearIter::TestClearIter(BitArray::Word* start, BitArray::Word* end) {
|
||||
for (auto* it = start; it != end; ++it) {
|
||||
if (*it != 0) {
|
||||
auto idx = CountTrailingZeros(*it);
|
||||
idx += 8 * int(intptr_t(it) - intptr_t(start)) & ClearMask;
|
||||
m_bit = idx;
|
||||
m_current_word = it;
|
||||
m_last_word = end;
|
||||
m_next = *it & (*it - 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SetInvalid();
|
||||
}
|
||||
|
||||
BitArray::TestClearIter& BitArray::TestClearIter::operator++() {
|
||||
m_bit &= ClearMask;
|
||||
|
||||
if (m_next != 0) {
|
||||
m_bit += CountTrailingZeros(m_next);
|
||||
m_next &= m_next - 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
*m_current_word = 0;
|
||||
++m_current_word;
|
||||
for (; m_current_word != m_last_word; *m_current_word = 0, ++m_current_word) {
|
||||
m_bit += NumBitsPerWord;
|
||||
if (*m_current_word == 0)
|
||||
continue;
|
||||
m_bit += CountTrailingZeros(*m_current_word);
|
||||
m_next = *m_current_word & (*m_current_word - 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SetInvalid();
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace ore
|
||||
@@ -0,0 +1,13 @@
|
||||
#include <algorithm>
|
||||
#include <ore/EnumUtil.h>
|
||||
|
||||
namespace ore {
|
||||
|
||||
int detail::EnumUtil::FindIndex(int value, const IterRange<const int*>& values) {
|
||||
auto it = std::find_if(values.begin(), values.end(), [value](int x) { return value == x; });
|
||||
if (it == values.end())
|
||||
return -1;
|
||||
return static_cast<int>(it - values.begin());
|
||||
}
|
||||
|
||||
} // namespace ore
|
||||
@@ -0,0 +1,106 @@
|
||||
#include <cstring>
|
||||
#include <ore/RelocationTable.h>
|
||||
|
||||
namespace ore {
|
||||
|
||||
namespace {
|
||||
|
||||
struct BitFlag32 {
|
||||
explicit BitFlag32(u32 flags) : m_flags(flags) {}
|
||||
bool operator[](int idx) const { return m_flags & (1 << idx); }
|
||||
|
||||
u32 m_flags{};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void RelocationTable::Section::SetPtr(void* ptr_) {
|
||||
ptr = reinterpret_cast<u64>(ptr_);
|
||||
}
|
||||
|
||||
void* RelocationTable::Section::GetPtr() const {
|
||||
return reinterpret_cast<void*>(ptr);
|
||||
}
|
||||
|
||||
void* RelocationTable::Section::GetPtrInFile(void* base) const {
|
||||
return static_cast<char*>(base) + offset;
|
||||
}
|
||||
|
||||
void* RelocationTable::Section::GetBasePtr(void* base) const {
|
||||
if (ptr)
|
||||
base = reinterpret_cast<void*>(ptr - offset);
|
||||
return base;
|
||||
}
|
||||
|
||||
u32 RelocationTable::Section::GetSize() const {
|
||||
return size;
|
||||
}
|
||||
|
||||
void RelocationTable::Relocate() {
|
||||
char* const table_base = reinterpret_cast<char*>(this) - table_start_offset;
|
||||
const auto* entries = GetEntries();
|
||||
const int num = num_sections;
|
||||
|
||||
for (int section_idx = 0; section_idx < num; ++section_idx) {
|
||||
const auto& section = GetSections()[section_idx];
|
||||
|
||||
auto* base = static_cast<char*>(section.GetBasePtr(table_base));
|
||||
const int idx0 = section.first_entry_idx;
|
||||
const int end = idx0 + section.num_entries;
|
||||
|
||||
for (int idx = idx0; idx < end; ++idx) {
|
||||
const auto& entry = entries[idx];
|
||||
const auto pointers_offset = entry.pointers_offset;
|
||||
const BitFlag32 mask{entry.mask};
|
||||
|
||||
auto* pointer_ptr = reinterpret_cast<u64*>(table_base + pointers_offset);
|
||||
for (int i = 0; i < 32; ++i, ++pointer_ptr) {
|
||||
if (!mask[i])
|
||||
continue;
|
||||
const auto offset = static_cast<int>(*pointer_ptr);
|
||||
void* ptr = offset == 0 ? nullptr : reinterpret_cast<void*>(base + offset);
|
||||
std::memcpy(pointer_ptr, &ptr, sizeof(ptr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RelocationTable::Unrelocate() {
|
||||
char* const table_base = reinterpret_cast<char*>(this) - table_start_offset;
|
||||
const auto* entries = GetEntries();
|
||||
const int num = num_sections;
|
||||
|
||||
for (int section_idx = 0; section_idx < num; ++section_idx) {
|
||||
auto& section = GetSections()[section_idx];
|
||||
|
||||
auto* base = static_cast<char*>(section.GetBasePtr(table_base));
|
||||
section.SetPtr(nullptr);
|
||||
const int idx0 = section.first_entry_idx;
|
||||
const int end = idx0 + section.num_entries;
|
||||
|
||||
for (int idx = idx0; idx < end; ++idx) {
|
||||
const auto& entry = entries[idx];
|
||||
const auto pointers_offset = entry.pointers_offset;
|
||||
const BitFlag32 mask{entry.mask};
|
||||
|
||||
auto* pointer_ptr = reinterpret_cast<void**>(table_base + pointers_offset);
|
||||
for (int i = 0; i < 32; ++i, ++pointer_ptr) {
|
||||
if (!mask[i])
|
||||
continue;
|
||||
void* ptr = *pointer_ptr;
|
||||
u64 offset = static_cast<int>(ptr == nullptr ? 0 : intptr_t(ptr) - intptr_t(base));
|
||||
std::memcpy(pointer_ptr, &offset, sizeof(offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int RelocationTable::CalcSize(int num_sections, int num_entries) {
|
||||
int size = 0;
|
||||
size += offsetof(RelocationTable, sections);
|
||||
size += sizeof(Section) * num_sections;
|
||||
size += sizeof(Section::Entry) * num_entries;
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace ore
|
||||
@@ -0,0 +1,50 @@
|
||||
#include <algorithm>
|
||||
#include <ore/ResDic.h>
|
||||
#include <ore/ResEndian.h>
|
||||
|
||||
namespace ore {
|
||||
|
||||
int ResDic::FindRefBit(const StringView& str1, const StringView& str2) {
|
||||
const auto len1 = str1.size();
|
||||
const auto len2 = str2.size();
|
||||
const auto len = std::max(len1, len2);
|
||||
|
||||
for (int bit_idx = 0; bit_idx < 8 * len; ++bit_idx) {
|
||||
const int idx = bit_idx >> 3;
|
||||
|
||||
int bit1 = 0;
|
||||
if (len1 > idx)
|
||||
bit1 = str1[len1 + -(idx + 1)] >> (bit_idx % 8) & 1;
|
||||
|
||||
int bit2 = 0;
|
||||
if (len2 > idx)
|
||||
bit2 = str2[len2 + -(idx + 1)] >> (bit_idx % 8) & 1;
|
||||
|
||||
if (bit1 != bit2)
|
||||
return bit_idx;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void SwapEndian(ResEndian* endian, ResDic* dic) {
|
||||
const auto swap_entries = [&] {
|
||||
const int num_entries = dic->num_entries + 1;
|
||||
for (int i = 0; i < num_entries; ++i) {
|
||||
ResDicEntry& entry = dic->GetEntries()[i];
|
||||
SwapEndian(&entry.compact_bit_idx);
|
||||
SwapEndian(&entry.next_indices[0]);
|
||||
SwapEndian(&entry.next_indices[1]);
|
||||
}
|
||||
};
|
||||
|
||||
if (endian->is_serializing) {
|
||||
swap_entries();
|
||||
SwapEndian(&dic->num_entries);
|
||||
} else {
|
||||
SwapEndian(&dic->num_entries);
|
||||
swap_entries();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ore
|
||||
@@ -0,0 +1,89 @@
|
||||
#include <ore/ResDic.h>
|
||||
#include <ore/ResEndian.h>
|
||||
#include <ore/ResMetaData.h>
|
||||
|
||||
namespace ore {
|
||||
|
||||
static void SwapEndianImpl(ResEndian* endian, ResMetaData* res) {
|
||||
auto* dictionary = res->dictionary.ToPtr(endian->base);
|
||||
if (dictionary)
|
||||
SwapEndian(endian, dictionary);
|
||||
|
||||
switch (res->type) {
|
||||
case ResMetaData::DataType::kArgument:
|
||||
case ResMetaData::DataType::kString:
|
||||
case ResMetaData::DataType::kStringArray:
|
||||
case ResMetaData::DataType::kActorIdentifier: {
|
||||
if (res->num_items == 0)
|
||||
break;
|
||||
BinString* str = res->value.str.ToPtr(endian->base);
|
||||
if (endian->is_serializing) {
|
||||
for (int i = 0, n = res->num_items; i < n; ++i) {
|
||||
auto* next = str->NextString();
|
||||
SwapEndian(&str->length);
|
||||
str = next;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0, n = res->num_items; i < n; ++i) {
|
||||
SwapEndian(&str->length);
|
||||
str = str->NextString();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ResMetaData::DataType::kContainer: {
|
||||
for (int i = 0, n = res->num_items; i < n; ++i) {
|
||||
ResMetaData* ptr = (&res->value.container + i)->ToPtr(endian->base);
|
||||
if (ptr)
|
||||
SwapEndian(endian, ptr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ResMetaData::DataType::kInt:
|
||||
case ResMetaData::DataType::kBool:
|
||||
case ResMetaData::DataType::kFloat:
|
||||
case ResMetaData::DataType::kIntArray:
|
||||
case ResMetaData::DataType::kFloatArray:
|
||||
for (int i = 0, n = res->num_items; i < n; ++i) {
|
||||
SwapEndian(&res->value.i + i);
|
||||
}
|
||||
break;
|
||||
case ResMetaData::DataType::kWString:
|
||||
case ResMetaData::DataType::kWStringArray: {
|
||||
if (res->num_items == 0)
|
||||
break;
|
||||
BinWString* str = res->value.wstr.ToPtr(endian->base);
|
||||
if (endian->is_serializing) {
|
||||
for (int i = 0, n = res->num_items; i < n; ++i) {
|
||||
for (auto& c : *str)
|
||||
c = static_cast<wchar_t>(SwapEndian(static_cast<u32>(c)));
|
||||
auto* next = str->NextString();
|
||||
SwapEndian(&str->length);
|
||||
str = next;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0, n = res->num_items; i < n; ++i) {
|
||||
SwapEndian(&str->length);
|
||||
for (auto& c : *str)
|
||||
c = static_cast<wchar_t>(SwapEndian(static_cast<u32>(c)));
|
||||
str = str->NextString();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ResMetaData::DataType::kBoolArray:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SwapEndian(ResEndian* endian, ResMetaData* res) {
|
||||
if (endian->is_serializing) {
|
||||
SwapEndianImpl(endian, res);
|
||||
SwapEndian(&res->num_items);
|
||||
} else {
|
||||
SwapEndian(&res->num_items);
|
||||
SwapEndianImpl(endian, res);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ore
|
||||
@@ -0,0 +1,13 @@
|
||||
#include <ore/StringPool.h>
|
||||
|
||||
namespace ore {
|
||||
|
||||
int StringPool::GetLength() const {
|
||||
return length;
|
||||
}
|
||||
|
||||
void StringPool::SetLength(int len) {
|
||||
length = len;
|
||||
}
|
||||
|
||||
} // namespace ore
|
||||
Reference in New Issue
Block a user