diff --git a/.github/workflows/Build_Plugin_GTA_2.yml b/.github/workflows/Build_Plugin_GTA_2.yml index fad917830..f3b62f674 100644 --- a/.github/workflows/Build_Plugin_GTA_2.yml +++ b/.github/workflows/Build_Plugin_GTA_2.yml @@ -36,11 +36,11 @@ jobs: # skip on pull request inside forks if: github.event_name != 'pull_request' || github.repository == 'DK22Pac/plugin-sdk' - runs-on: windows-2025-vs2026 + runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: "recursive" diff --git a/.github/workflows/Build_Plugin_GTA_2_clang.yml b/.github/workflows/Build_Plugin_GTA_2_clang.yml index 58fd94110..6a25d35af 100644 --- a/.github/workflows/Build_Plugin_GTA_2_clang.yml +++ b/.github/workflows/Build_Plugin_GTA_2_clang.yml @@ -40,7 +40,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: "recursive" diff --git a/.github/workflows/Build_Plugin_GTA_3.yml b/.github/workflows/Build_Plugin_GTA_3.yml index 5da681d77..4cc36b863 100644 --- a/.github/workflows/Build_Plugin_GTA_3.yml +++ b/.github/workflows/Build_Plugin_GTA_3.yml @@ -36,11 +36,11 @@ jobs: # skip on pull request inside forks if: github.event_name != 'pull_request' || github.repository == 'DK22Pac/plugin-sdk' - runs-on: windows-2025-vs2026 + runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: "recursive" diff --git a/.github/workflows/Build_Plugin_GTA_3_clang.yml b/.github/workflows/Build_Plugin_GTA_3_clang.yml index 2a396b732..b62da5eef 100644 --- a/.github/workflows/Build_Plugin_GTA_3_clang.yml +++ b/.github/workflows/Build_Plugin_GTA_3_clang.yml @@ -40,7 +40,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: "recursive" diff --git a/.github/workflows/Build_Plugin_GTA_4.yml b/.github/workflows/Build_Plugin_GTA_4.yml index 1bd9d0cc9..a361e25c9 100644 --- a/.github/workflows/Build_Plugin_GTA_4.yml +++ b/.github/workflows/Build_Plugin_GTA_4.yml @@ -36,7 +36,7 @@ jobs: # skip on pull request inside forks if: github.event_name != 'pull_request' || github.repository == 'DK22Pac/plugin-sdk' - runs-on: windows-2025-vs2026 + runs-on: windows-latest steps: - name: Checkout diff --git a/.github/workflows/Build_Plugin_GTA_4_clang.yml b/.github/workflows/Build_Plugin_GTA_4_clang.yml index ce7fbb6dc..b5c9304ae 100644 --- a/.github/workflows/Build_Plugin_GTA_4_clang.yml +++ b/.github/workflows/Build_Plugin_GTA_4_clang.yml @@ -40,7 +40,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: "recursive" diff --git a/.github/workflows/Build_Plugin_GTA_SA.yml b/.github/workflows/Build_Plugin_GTA_SA.yml index 4a8af952f..56501b44b 100644 --- a/.github/workflows/Build_Plugin_GTA_SA.yml +++ b/.github/workflows/Build_Plugin_GTA_SA.yml @@ -36,11 +36,11 @@ jobs: # skip on pull request inside forks if: github.event_name != 'pull_request' || github.repository == 'DK22Pac/plugin-sdk' - runs-on: windows-2025-vs2026 + runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: "recursive" diff --git a/.github/workflows/Build_Plugin_GTA_SA_clang.yml b/.github/workflows/Build_Plugin_GTA_SA_clang.yml index d54357c4b..2c2c3c477 100644 --- a/.github/workflows/Build_Plugin_GTA_SA_clang.yml +++ b/.github/workflows/Build_Plugin_GTA_SA_clang.yml @@ -40,7 +40,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: "recursive" diff --git a/.github/workflows/Build_Plugin_GTA_VC.yml b/.github/workflows/Build_Plugin_GTA_VC.yml index a8b7c7651..95efff0e8 100644 --- a/.github/workflows/Build_Plugin_GTA_VC.yml +++ b/.github/workflows/Build_Plugin_GTA_VC.yml @@ -36,11 +36,11 @@ jobs: # skip on pull request inside forks if: github.event_name != 'pull_request' || github.repository == 'DK22Pac/plugin-sdk' - runs-on: windows-2025-vs2026 + runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: "recursive" diff --git a/.github/workflows/Build_Plugin_GTA_VC_clang.yml b/.github/workflows/Build_Plugin_GTA_VC_clang.yml index 8956d1548..69e8eba10 100644 --- a/.github/workflows/Build_Plugin_GTA_VC_clang.yml +++ b/.github/workflows/Build_Plugin_GTA_VC_clang.yml @@ -40,7 +40,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: "recursive" diff --git a/.github/workflows/Update_Version_Info.yaml b/.github/workflows/Update_Version_Info.yaml index 060eb33c5..7e472a23d 100644 --- a/.github/workflows/Update_Version_Info.yaml +++ b/.github/workflows/Update_Version_Info.yaml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: master diff --git a/.github/workflows/Validate_File_Contents.yaml b/.github/workflows/Validate_File_Contents.yaml index a3d55117e..20f2ec804 100644 --- a/.github/workflows/Validate_File_Contents.yaml +++ b/.github/workflows/Validate_File_Contents.yaml @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Check files run: node .github/workflows/scripts/Validate_Contents.js "github" diff --git a/safetyhook/safetyhook.cpp b/safetyhook/safetyhook.cpp index 4f8a9ef0b..82e52bb28 100644 --- a/safetyhook/safetyhook.cpp +++ b/safetyhook/safetyhook.cpp @@ -2,7 +2,7 @@ #define NOMINMAX -#include +#include "safetyhook.hpp" // @@ -13,29 +13,10 @@ #include #include -#define NOMINMAX -#if __has_include() -#include -#elif __has_include() -#include -#else -#error "Windows.h not found" -#endif - -namespace safetyhook { -template constexpr T align_up(T address, size_t align) { - const auto unaligned_address = (uintptr_t)address; - const auto aligned_address = (unaligned_address + align - 1) & ~(align - 1); - return (T)aligned_address; -} -template constexpr T align_down(T address, size_t align) { - const auto unaligned_address = (uintptr_t)address; - const auto aligned_address = unaligned_address & ~(align - 1); - return (T)aligned_address; -} +namespace safetyhook { Allocation::Allocation(Allocation&& other) noexcept { *this = std::move(other); } @@ -110,16 +91,20 @@ void Allocator::free(uint8_t* address, size_t size) { std::expected Allocator::internal_allocate_near( const std::vector& desired_addresses, size_t size, size_t max_distance) { + // Align to 2 bytes to pass MFP virtual method check + // See https://itanium-cxx-abi.github.io/cxx-abi/abi.html#member-function-pointers + size_t aligned_size = align_up(size, 2); + // First search through our list of allocations for a free block that is large // enough. for (const auto& allocation : m_memory) { - if (allocation->size < size) { + if (allocation->size < aligned_size) { continue; } for (auto node = allocation->freelist.get(); node != nullptr; node = node->next.get()) { // Enough room? - if (static_cast(node->end - node->start) < size) { + if (static_cast(node->end - node->start) < aligned_size) { continue; } @@ -130,36 +115,35 @@ std::expected Allocator::internal_allocate_near( continue; } - node->start += size; + node->start += aligned_size; return Allocation{shared_from_this(), address, size}; } } // If we didn't find a free block, we need to allocate a new one. - SYSTEM_INFO si{}; - - GetSystemInfo(&si); - - const auto allocation_size = align_up(size, si.dwAllocationGranularity); - const auto allocation_address = allocate_nearby_memory(desired_addresses, allocation_size, max_distance); + auto allocation_size = align_up(aligned_size, system_info().allocation_granularity); + auto allocation_address = allocate_nearby_memory(desired_addresses, allocation_size, max_distance); if (!allocation_address) { return std::unexpected{allocation_address.error()}; } - const auto& allocation = m_memory.emplace_back(new Memory); + auto& allocation = m_memory.emplace_back(new Memory); allocation->address = *allocation_address; allocation->size = allocation_size; allocation->freelist = std::make_unique(); - allocation->freelist->start = *allocation_address + size; + allocation->freelist->start = *allocation_address + aligned_size; allocation->freelist->end = *allocation_address + allocation_size; return Allocation{shared_from_this(), *allocation_address, size}; } void Allocator::internal_free(uint8_t* address, size_t size) { + // See internal_allocate_near + size = align_up(size, 2); + for (const auto& allocation : m_memory) { if (allocation->address > address || allocation->address + allocation->size < address) { continue; @@ -209,9 +193,8 @@ void Allocator::combine_adjacent_freenodes(Memory& memory) { std::expected Allocator::allocate_nearby_memory( const std::vector& desired_addresses, size_t size, size_t max_distance) { if (desired_addresses.empty()) { - if (const auto result = VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); - result != nullptr) { - return static_cast(result); + if (auto result = vm_allocate(nullptr, size, VM_ACCESS_RWX)) { + return result.value(); } return std::unexpected{Error::BAD_VIRTUAL_ALLOC}; @@ -222,16 +205,17 @@ std::expected Allocator::allocate_nearby_memory( return nullptr; } - return static_cast(VirtualAlloc(p, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)); - }; - - SYSTEM_INFO si{}; + if (auto result = vm_allocate(p, size, VM_ACCESS_RWX)) { + return result.value(); + } - GetSystemInfo(&si); + return nullptr; + }; + auto si = system_info(); auto desired_address = desired_addresses[0]; - auto search_start = reinterpret_cast(std::numeric_limits::min()); - auto search_end = reinterpret_cast(std::numeric_limits::max()); + auto search_start = si.min_address; + auto search_end = si.max_address; if (static_cast(desired_address - search_start) > max_distance) { search_start = desired_address - max_distance; @@ -241,35 +225,42 @@ std::expected Allocator::allocate_nearby_memory( search_end = desired_address + max_distance; } - search_start = std::max(search_start, static_cast(si.lpMinimumApplicationAddress)); - search_end = std::min(search_end, static_cast(si.lpMaximumApplicationAddress)); - desired_address = align_up(desired_address, si.dwAllocationGranularity); - MEMORY_BASIC_INFORMATION mbi{}; + search_start = std::max(search_start, si.min_address); + search_end = std::min(search_end, si.max_address); + desired_address = align_up(desired_address, si.allocation_granularity); + VmBasicInfo mbi{}; // Search backwards from the desired_address. for (auto p = desired_address; p > search_start && in_range(p, desired_addresses, max_distance); - p = align_down(static_cast(mbi.AllocationBase) - 1, si.dwAllocationGranularity)) { - if (VirtualQuery(p, &mbi, sizeof(mbi)) == 0) { + p = align_down(mbi.address - 1, si.allocation_granularity)) { + auto result = vm_query(p); + + if (!result) { break; } - if (mbi.State != MEM_FREE) { + mbi = result.value(); + + if (!mbi.is_free) { continue; } - if (auto allocation_address = attempt_allocation(p); allocation_address != 0) { + if (auto allocation_address = attempt_allocation(p); allocation_address != nullptr) { return allocation_address; } } // Search forwards from the desired_address. - for (auto p = desired_address; p < search_end && in_range(p, desired_addresses, max_distance); - p += mbi.RegionSize) { - if (VirtualQuery(p, &mbi, sizeof(mbi)) == 0) { + for (auto p = desired_address; p < search_end && in_range(p, desired_addresses, max_distance); p += mbi.size) { + auto result = vm_query(p); + + if (!result) { break; } - if (mbi.State != MEM_FREE) { + mbi = result.value(); + + if (!mbi.is_free) { continue; } @@ -282,14 +273,14 @@ std::expected Allocator::allocate_nearby_memory( } bool Allocator::in_range(uint8_t* address, const std::vector& desired_addresses, size_t max_distance) { - return std::ranges::all_of(desired_addresses, [&](const auto& desired_address) { + return std::all_of(desired_addresses.begin(), desired_addresses.end(), [&](const auto& desired_address) { const size_t delta = (address > desired_address) ? address - desired_address : desired_address - address; return delta <= max_distance; }); } Allocator::Memory::~Memory() { - VirtualFree(address, 0, MEM_RELEASE); + vm_free(address); } } // namespace safetyhook @@ -299,16 +290,16 @@ Allocator::Memory::~Memory() { namespace safetyhook { -InlineHook create_inline(void* target, void* destination) { - if (auto hook = InlineHook::create(target, destination)) { +InlineHook create_inline(void* target, void* destination, InlineHook::Flags flags) { + if (auto hook = InlineHook::create(target, destination, flags)) { return std::move(*hook); } else { return {}; } } -MidHook create_mid(void* target, MidHookFn destination) { - if (auto hook = MidHook::create(target, destination)) { +MidHook create_mid(void* target, MidHookFn destination, MidHook::Flags flags) { + if (auto hook = MidHook::create(target, destination, flags)) { return std::move(*hook); } else { return {}; @@ -330,18 +321,10 @@ VmtHook create_vmt(void* object) { #include -#if __has_include() -#include -#elif __has_include() -#include -#else -#error "Windows.h not found" -#endif - -#if __has_include() -#include -#elif __has_include() -#include +#if __has_include("Zydis/Zydis.h") +#include "Zydis/Zydis.h" +#elif __has_include("Zydis.h") +#include "Zydis.h" #else #error "Zydis not found" #endif @@ -356,7 +339,7 @@ struct JmpE9 { uint32_t offset{0}; }; -#if defined(_M_X64) +#if SAFETYHOOK_ARCH_X86_64 struct JmpFF { uint8_t opcode0{0xFF}; uint8_t opcode1{0x25}; @@ -373,7 +356,7 @@ struct TrampolineEpilogueFF { JmpFF jmp_to_original{}; uint64_t original_address{}; }; -#elif defined(_M_IX86) +#elif SAFETYHOOK_ARCH_X86_32 struct TrampolineEpilogueE9 { JmpE9 jmp_to_original{}; JmpE9 jmp_to_destination{}; @@ -381,7 +364,7 @@ struct TrampolineEpilogueE9 { #endif #pragma pack(pop) -#ifdef _M_X64 +#if SAFETYHOOK_ARCH_X86_64 static auto make_jmp_ff(uint8_t* src, uint8_t* dst, uint8_t* data) { JmpFF jmp{}; @@ -397,12 +380,6 @@ static auto make_jmp_ff(uint8_t* src, uint8_t* dst, uint8_t* data) { return std::unexpected{InlineHook::Error::not_enough_space(dst)}; } - auto um = unprotect(src, size); - - if (!um) { - return std::unexpected{InlineHook::Error::failed_to_unprotect(src)}; - } - if (size > sizeof(JmpFF)) { std::fill_n(src, size, static_cast(0x90)); } @@ -427,12 +404,6 @@ constexpr auto make_jmp_e9(uint8_t* src, uint8_t* dst) { return std::unexpected{InlineHook::Error::not_enough_space(dst)}; } - auto um = unprotect(src, size); - - if (!um) { - return std::unexpected{InlineHook::Error::failed_to_unprotect(src)}; - } - if (size > sizeof(JmpE9)) { std::fill_n(src, size, static_cast(0x90)); } @@ -446,12 +417,10 @@ static bool decode(ZydisDecodedInstruction* ix, uint8_t* ip) { ZydisDecoder decoder{}; ZyanStatus status; -#if defined(_M_X64) +#if SAFETYHOOK_ARCH_X86_64 status = ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64); -#elif defined(_M_IX86) +#elif SAFETYHOOK_ARCH_X86_32 status = ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LEGACY_32, ZYDIS_STACK_WIDTH_32); -#else -#error "Unsupported architecture" #endif if (!ZYAN_SUCCESS(status)) { @@ -461,12 +430,12 @@ static bool decode(ZydisDecodedInstruction* ix, uint8_t* ip) { return ZYAN_SUCCESS(ZydisDecoderDecodeInstruction(&decoder, nullptr, ip, 15, ix)); } -std::expected InlineHook::create(void* target, void* destination) { - return create(Allocator::global(), target, destination); +std::expected InlineHook::create(void* target, void* destination, Flags flags) { + return create(Allocator::global(), target, destination, flags); } std::expected InlineHook::create( - const std::shared_ptr& allocator, void* target, void* destination) { + const std::shared_ptr& allocator, void* target, void* destination, Flags flags) { InlineHook hook{}; if (const auto setup_result = @@ -475,6 +444,12 @@ std::expected InlineHook::create( return std::unexpected{setup_result.error()}; } + if (!(flags & StartDisabled)) { + if (auto enable_result = hook.enable(); !enable_result) { + return std::unexpected{enable_result.error()}; + } + } + return hook; } @@ -493,10 +468,14 @@ InlineHook& InlineHook::operator=(InlineHook&& other) noexcept { m_trampoline = std::move(other.m_trampoline); m_trampoline_size = other.m_trampoline_size; m_original_bytes = std::move(other.m_original_bytes); + m_enabled = other.m_enabled; + m_type = other.m_type; other.m_target = nullptr; other.m_destination = nullptr; other.m_trampoline_size = 0; + other.m_enabled = false; + other.m_type = Type::Unset; } return *this; @@ -516,11 +495,11 @@ std::expected InlineHook::setup( m_destination = destination; if (auto e9_result = e9_hook(allocator); !e9_result) { -#ifdef _M_X64 +#if SAFETYHOOK_ARCH_X86_64 if (auto ff_result = ff_hook(allocator); !ff_result) { return ff_result; } -#else +#elif SAFETYHOOK_ARCH_X86_32 return e9_result; #endif } @@ -599,7 +578,7 @@ std::expected InlineHook::e9_hook(const std::shared_ptr auto new_disp = target_address - (tramp_ip + 6); // Handle the case where the target is now in the trampoline. - if (target_address < m_target + m_original_bytes.size()) { + if (target_address >= m_target && target_address < m_target + m_original_bytes.size()) { new_disp = static_cast(ix.raw.imm[0].value.s); } @@ -612,7 +591,7 @@ std::expected InlineHook::e9_hook(const std::shared_ptr auto new_disp = target_address - (tramp_ip + 5); // Handle the case where the target is now in the trampoline. - if (target_address < m_target + m_original_bytes.size()) { + if (target_address >= m_target && target_address < m_target + m_original_bytes.size()) { new_disp = static_cast(ix.raw.imm[0].value.s); } @@ -640,43 +619,24 @@ std::expected InlineHook::e9_hook(const std::shared_ptr src = reinterpret_cast(&trampoline_epilogue->jmp_to_destination); dst = m_destination; -#ifdef _M_X64 +#if SAFETYHOOK_ARCH_X86_64 auto data = reinterpret_cast(&trampoline_epilogue->destination_address); if (auto result = emit_jmp_ff(src, dst, data); !result) { return std::unexpected{result.error()}; } -#else +#elif SAFETYHOOK_ARCH_X86_32 if (auto result = emit_jmp_e9(src, dst); !result) { return std::unexpected{result.error()}; } #endif - std::optional error; - - // jmp from original to trampoline. - execute_while_frozen( - [this, &trampoline_epilogue, &error] { - if (auto result = emit_jmp_e9(m_target, - reinterpret_cast(&trampoline_epilogue->jmp_to_destination), m_original_bytes.size()); - !result) { - error = result.error(); - } - }, - [this](auto, auto, auto ctx) { - for (size_t i = 0; i < m_original_bytes.size(); ++i) { - fix_ip(ctx, m_target + i, m_trampoline.data() + i); - } - }); - - if (error) { - return std::unexpected{*error}; - } + m_type = Type::E9; return {}; } -#ifdef _M_X64 +#if SAFETYHOOK_ARCH_X86_64 std::expected InlineHook::ff_hook(const std::shared_ptr& allocator) { m_original_bytes.clear(); m_trampoline_size = sizeof(TrampolineEpilogueFF); @@ -720,49 +680,77 @@ std::expected InlineHook::ff_hook(const std::shared_ptr return std::unexpected{result.error()}; } + m_type = Type::FF; + + return {}; +} +#endif + +std::expected InlineHook::enable() { + std::scoped_lock lock{m_mutex}; + + if (m_enabled) { + return {}; + } + std::optional error; // jmp from original to trampoline. - execute_while_frozen( - [this, &error] { - if (auto result = emit_jmp_ff(m_target, m_destination, m_target + sizeof(JmpFF), m_original_bytes.size()); + trap_threads(m_target, m_trampoline.data(), m_original_bytes.size(), [this, &error] { + if (m_type == Type::E9) { + auto trampoline_epilogue = reinterpret_cast( + m_trampoline.address() + m_trampoline_size - sizeof(TrampolineEpilogueE9)); + + if (auto result = emit_jmp_e9(m_target, + reinterpret_cast(&trampoline_epilogue->jmp_to_destination), m_original_bytes.size()); !result) { error = result.error(); } - }, - [this](auto, auto, auto ctx) { - for (size_t i = 0; i < m_original_bytes.size(); ++i) { - fix_ip(ctx, m_target + i, m_trampoline.data() + i); + } + +#if SAFETYHOOK_ARCH_X86_64 + if (m_type == Type::FF) { + if (auto result = emit_jmp_ff(m_target, m_destination, m_target + sizeof(JmpFF), m_original_bytes.size()); + !result) { + error = result.error(); } - }); + } +#endif + }); if (error) { return std::unexpected{*error}; } + m_enabled = true; + + return {}; +} + +std::expected InlineHook::disable() { + std::scoped_lock lock{m_mutex}; + + if (!m_enabled) { + return {}; + } + + trap_threads(m_trampoline.data(), m_target, m_original_bytes.size(), + [this] { std::copy(m_original_bytes.begin(), m_original_bytes.end(), m_target); }); + + m_enabled = false; + return {}; } -#endif void InlineHook::destroy() { + [[maybe_unused]] auto disable_result = disable(); + std::scoped_lock lock{m_mutex}; if (!m_trampoline) { return; } - execute_while_frozen( - [this] { - if (auto um = unprotect(m_target, m_original_bytes.size())) { - std::copy(m_original_bytes.begin(), m_original_bytes.end(), m_target); - } - }, - [this](auto, auto, auto ctx) { - for (size_t i = 0; i < m_original_bytes.size(); ++i) { - fix_ip(ctx, m_trampoline.data() + i, m_target + i); - } - }); - m_trampoline.free(); } } // namespace safetyhook @@ -778,7 +766,8 @@ void InlineHook::destroy() { namespace safetyhook { -#ifdef _M_X64 +#if SAFETYHOOK_ARCH_X86_64 +#if SAFETYHOOK_OS_WINDOWS constexpr std::array asm_data = {0xFF, 0x35, 0x79, 0x01, 0x00, 0x00, 0x54, 0x54, 0x55, 0x50, 0x53, 0x51, 0x52, 0x56, 0x57, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x9C, 0x48, 0x81, 0xEC, 0x00, 0x01, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0xBC, 0x24, 0xF0, 0x00, 0x00, 0x00, 0xF3, @@ -800,7 +789,30 @@ constexpr std::array asm_data = {0xFF, 0x35, 0x79, 0x01, 0x00, 0x0 0x00, 0x00, 0x48, 0x81, 0xC4, 0x00, 0x01, 0x00, 0x00, 0x9D, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5F, 0x5E, 0x5A, 0x59, 0x5B, 0x58, 0x5D, 0x48, 0x8D, 0x64, 0x24, 0x08, 0x5C, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; -#else +#elif SAFETYHOOK_OS_LINUX +constexpr std::array asm_data = {0xFF, 0x35, 0x79, 0x01, 0x00, 0x00, 0x54, 0x54, 0x55, 0x50, 0x53, 0x51, + 0x52, 0x56, 0x57, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, + 0x9C, 0x48, 0x81, 0xEC, 0x00, 0x01, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0xBC, 0x24, 0xF0, 0x00, 0x00, 0x00, 0xF3, + 0x44, 0x0F, 0x7F, 0xB4, 0x24, 0xE0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0xAC, 0x24, 0xD0, 0x00, 0x00, 0x00, + 0xF3, 0x44, 0x0F, 0x7F, 0xA4, 0x24, 0xC0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x9C, 0x24, 0xB0, 0x00, 0x00, + 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x94, 0x24, 0xA0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x8C, 0x24, 0x90, 0x00, + 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00, 0xF3, 0x0F, 0x7F, 0x7C, 0x24, 0x70, 0xF3, + 0x0F, 0x7F, 0x74, 0x24, 0x60, 0xF3, 0x0F, 0x7F, 0x6C, 0x24, 0x50, 0xF3, 0x0F, 0x7F, 0x64, 0x24, 0x40, 0xF3, 0x0F, + 0x7F, 0x5C, 0x24, 0x30, 0xF3, 0x0F, 0x7F, 0x54, 0x24, 0x20, 0xF3, 0x0F, 0x7F, 0x4C, 0x24, 0x10, 0xF3, 0x0F, 0x7F, + 0x04, 0x24, 0x48, 0x8B, 0xBC, 0x24, 0x80, 0x01, 0x00, 0x00, 0x48, 0x83, 0xC7, 0x10, 0x48, 0x89, 0xBC, 0x24, 0x80, + 0x01, 0x00, 0x00, 0x48, 0x8D, 0x3C, 0x24, 0x48, 0x89, 0xE3, 0x48, 0x83, 0xEC, 0x30, 0x48, 0x83, 0xE4, 0xF0, 0xFF, + 0x15, 0xA8, 0x00, 0x00, 0x00, 0x48, 0x89, 0xDC, 0xF3, 0x0F, 0x6F, 0x04, 0x24, 0xF3, 0x0F, 0x6F, 0x4C, 0x24, 0x10, + 0xF3, 0x0F, 0x6F, 0x54, 0x24, 0x20, 0xF3, 0x0F, 0x6F, 0x5C, 0x24, 0x30, 0xF3, 0x0F, 0x6F, 0x64, 0x24, 0x40, 0xF3, + 0x0F, 0x6F, 0x6C, 0x24, 0x50, 0xF3, 0x0F, 0x6F, 0x74, 0x24, 0x60, 0xF3, 0x0F, 0x6F, 0x7C, 0x24, 0x70, 0xF3, 0x44, + 0x0F, 0x6F, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0x8C, 0x24, 0x90, 0x00, 0x00, 0x00, 0xF3, + 0x44, 0x0F, 0x6F, 0x94, 0x24, 0xA0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0x9C, 0x24, 0xB0, 0x00, 0x00, 0x00, + 0xF3, 0x44, 0x0F, 0x6F, 0xA4, 0x24, 0xC0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0xAC, 0x24, 0xD0, 0x00, 0x00, + 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0xB4, 0x24, 0xE0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0xBC, 0x24, 0xF0, 0x00, + 0x00, 0x00, 0x48, 0x81, 0xC4, 0x00, 0x01, 0x00, 0x00, 0x9D, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x41, + 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5F, 0x5E, 0x5A, 0x59, 0x5B, 0x58, 0x5D, 0x48, 0x8D, 0x64, 0x24, 0x08, + 0x5C, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +#endif +#elif SAFETYHOOK_ARCH_X86_32 constexpr std::array asm_data = {0xFF, 0x35, 0xA7, 0x00, 0x00, 0x00, 0x54, 0x54, 0x55, 0x50, 0x53, 0x51, 0x52, 0x56, 0x57, 0x9C, 0x81, 0xEC, 0x80, 0x00, 0x00, 0x00, 0xF3, 0x0F, 0x7F, 0x7C, 0x24, 0x70, 0xF3, 0x0F, 0x7F, 0x74, 0x24, 0x60, 0xF3, 0x0F, 0x7F, 0x6C, 0x24, 0x50, 0xF3, 0x0F, 0x7F, 0x64, 0x24, 0x40, 0xF3, 0x0F, 0x7F, 0x5C, @@ -813,12 +825,12 @@ constexpr std::array asm_data = {0xFF, 0x35, 0xA7, 0x00, 0x00, 0x0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; #endif -std::expected MidHook::create(void* target, MidHookFn destination) { - return create(Allocator::global(), target, destination); +std::expected MidHook::create(void* target, MidHookFn destination, Flags flags) { + return create(Allocator::global(), target, destination, flags); } std::expected MidHook::create( - const std::shared_ptr& allocator, void* target, MidHookFn destination) { + const std::shared_ptr& allocator, void* target, MidHookFn destination, Flags flags) { MidHook hook{}; if (const auto setup_result = hook.setup(allocator, reinterpret_cast(target), destination); @@ -826,6 +838,12 @@ std::expected MidHook::create( return std::unexpected{setup_result.error()}; } + if (!(flags & StartDisabled)) { + if (auto enable_result = hook.enable(); !enable_result) { + return std::unexpected{enable_result.error()}; + } + } + return hook; } @@ -866,9 +884,9 @@ std::expected MidHook::setup( std::copy(asm_data.begin(), asm_data.end(), m_stub.data()); -#ifdef _M_X64 +#if SAFETYHOOK_ARCH_X86_64 store(m_stub.data() + sizeof(asm_data) - 16, m_destination); -#else +#elif SAFETYHOOK_ARCH_X86_32 store(m_stub.data() + sizeof(asm_data) - 8, m_destination); // 32-bit has some relocations we need to fix up as well. @@ -876,7 +894,7 @@ std::expected MidHook::setup( store(m_stub.data() + 0x59, m_stub.data() + m_stub.size() - 8); #endif - auto hook_result = InlineHook::create(allocator, m_target, m_stub.data()); + auto hook_result = InlineHook::create(allocator, m_target, m_stub.data(), InlineHook::StartDisabled); if (!hook_result) { m_stub.free(); @@ -885,163 +903,242 @@ std::expected MidHook::setup( m_hook = std::move(*hook_result); -#ifdef _M_X64 +#if SAFETYHOOK_ARCH_X86_64 store(m_stub.data() + sizeof(asm_data) - 8, m_hook.trampoline().data()); -#else +#elif SAFETYHOOK_ARCH_X86_32 store(m_stub.data() + sizeof(asm_data) - 4, m_hook.trampoline().data()); #endif return {}; } + +std::expected MidHook::enable() { + if (auto enable_result = m_hook.enable(); !enable_result) { + return std::unexpected{Error::bad_inline_hook(enable_result.error())}; + } + + return {}; +} + +std::expected MidHook::disable() { + if (auto disable_result = m_hook.disable(); !disable_result) { + return std::unexpected{Error::bad_inline_hook(disable_result.error())}; + } + + return {}; +} } // namespace safetyhook // -// Source file: thread_freezer.cpp +// Source file: os.linux.cpp // -#if __has_include() -#include -#elif __has_include() -#include -#else -#error "Windows.h not found" -#endif -#include +#if SAFETYHOOK_OS_LINUX + +#include + +#include +#include -#pragma comment(lib, "ntdll") -extern "C" { -NTSTATUS -NTAPI -NtGetNextThread(HANDLE ProcessHandle, HANDLE ThreadHandle, ACCESS_MASK DesiredAccess, ULONG HandleAttributes, - ULONG Flags, PHANDLE NewThreadHandle); -} namespace safetyhook { -void execute_while_frozen( - const std::function& run_fn, const std::function& visit_fn) { - // Freeze all threads. - int num_threads_frozen; - auto first_run = true; - - do { - num_threads_frozen = 0; - HANDLE thread{}; - - while (true) { - HANDLE next_thread{}; - const auto status = NtGetNextThread(GetCurrentProcess(), thread, - THREAD_QUERY_LIMITED_INFORMATION | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT, 0, - 0, &next_thread); - - if (thread != nullptr) { - CloseHandle(thread); - } +std::expected vm_allocate(uint8_t* address, size_t size, VmAccess access) { + int prot = 0; + int flags = MAP_PRIVATE | MAP_ANONYMOUS; + + if (access == VM_ACCESS_R) { + prot = PROT_READ; + } else if (access == VM_ACCESS_RW) { + prot = PROT_READ | PROT_WRITE; + } else if (access == VM_ACCESS_RX) { + prot = PROT_READ | PROT_EXEC; + } else if (access == VM_ACCESS_RWX) { + prot = PROT_READ | PROT_WRITE | PROT_EXEC; + } else { + return std::unexpected{OsError::FAILED_TO_ALLOCATE}; + } - if (!NT_SUCCESS(status)) { - break; - } + auto* result = mmap(address, size, prot, flags, -1, 0); - thread = next_thread; + if (result == MAP_FAILED) { + return std::unexpected{OsError::FAILED_TO_ALLOCATE}; + } - const auto thread_id = GetThreadId(thread); + return static_cast(result); +} - if (thread_id == 0 || thread_id == GetCurrentThreadId()) { - continue; - } +void vm_free(uint8_t* address) { + munmap(address, 0); +} - const auto suspend_count = SuspendThread(thread); +std::expected vm_protect(uint8_t* address, size_t size, VmAccess access) { + int prot = 0; - if (suspend_count == static_cast(-1)) { - continue; - } + if (access == VM_ACCESS_R) { + prot = PROT_READ; + } else if (access == VM_ACCESS_RW) { + prot = PROT_READ | PROT_WRITE; + } else if (access == VM_ACCESS_RX) { + prot = PROT_READ | PROT_EXEC; + } else if (access == VM_ACCESS_RWX) { + prot = PROT_READ | PROT_WRITE | PROT_EXEC; + } else { + return std::unexpected{OsError::FAILED_TO_PROTECT}; + } - // Check if the thread was already frozen. Only resume if the thread was already frozen, and it wasn't the - // first run of this freeze loop to account for threads that may have already been frozen for other reasons. - if (suspend_count != 0 && !first_run) { - ResumeThread(thread); - continue; - } + return vm_protect(address, size, prot); +} - CONTEXT thread_ctx{}; +std::expected vm_protect(uint8_t* address, size_t size, uint32_t protect) { + auto mbi = vm_query(address); - thread_ctx.ContextFlags = CONTEXT_FULL; + if (!mbi.has_value()) { + return std::unexpected{OsError::FAILED_TO_PROTECT}; + } - if (GetThreadContext(thread, &thread_ctx) == FALSE) { - continue; - } + uint32_t old_protect = 0; - if (visit_fn) { - visit_fn(static_cast(thread_id), static_cast(thread), - static_cast(&thread_ctx)); - } + if (mbi->access.read) { + old_protect |= PROT_READ; + } - ++num_threads_frozen; - } + if (mbi->access.write) { + old_protect |= PROT_WRITE; + } + + if (mbi->access.execute) { + old_protect |= PROT_EXEC; + } - first_run = false; - } while (num_threads_frozen != 0); + auto* addr = align_down(address, static_cast(sysconf(_SC_PAGESIZE))); - // Run the function. - if (run_fn) { - run_fn(); + size = size + static_cast(address - addr); + + if (mprotect(addr, size, static_cast(protect)) == -1) { + return std::unexpected{OsError::FAILED_TO_PROTECT}; } - // Resume all threads. - HANDLE thread{}; + return old_protect; +} - while (true) { - HANDLE next_thread{}; - const auto status = NtGetNextThread(GetCurrentProcess(), thread, - THREAD_QUERY_LIMITED_INFORMATION | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT, 0, 0, - &next_thread); +std::expected vm_query(uint8_t* address) { + auto* maps = fopen("/proc/self/maps", "r"); - if (thread != nullptr) { - CloseHandle(thread); - } + if (maps == nullptr) { + return std::unexpected{OsError::FAILED_TO_QUERY}; + } + + char line[512]; + unsigned long start; + unsigned long end; + char perms[5]; + unsigned long offset; + int dev_major; + int dev_minor; + unsigned long inode; + char path[256]; + unsigned long last_end = + reinterpret_cast(system_info().min_address); // Track the end address of the last mapping. + auto addr = reinterpret_cast(address); + std::optional info = std::nullopt; + + while (fgets(line, sizeof(line), maps) != nullptr) { + path[0] = '\0'; + + sscanf(line, "%lx-%lx %4s %lx %x:%x %lu %255[^\n]", &start, &end, perms, &offset, &dev_major, &dev_minor, + &inode, path); + + if (last_end < start && addr >= last_end && addr < start) { + info = std::make_optional( + {reinterpret_cast(last_end), start - last_end, VmAccess{}, true}); - if (!NT_SUCCESS(status)) { break; } - thread = next_thread; + last_end = end; - const auto thread_id = GetThreadId(thread); + if (addr >= start && addr < end) { + info = std::make_optional({reinterpret_cast(start), end - start, VmAccess{}, false}); - if (thread_id == 0 || thread_id == GetCurrentThreadId()) { - continue; + if (perms[0] == 'r') { + info->access.read = true; + } + + if (perms[1] == 'w') { + info->access.write = true; + } + + if (perms[2] == 'x') { + info->access.execute = true; + } + + break; } + } + + fclose(maps); - ResumeThread(thread); + if (!info.has_value()) { + return std::unexpected{OsError::FAILED_TO_QUERY}; } + + return info.value(); } -void fix_ip(ThreadContext thread_ctx, uint8_t* old_ip, uint8_t* new_ip) { - auto* ctx = reinterpret_cast(thread_ctx); +bool vm_is_readable(uint8_t* address, [[maybe_unused]] size_t size) { + return vm_query(address).value_or(VmBasicInfo{}).access.read; +} -#ifdef _M_X64 - auto ip = ctx->Rip; -#else - auto ip = ctx->Eip; -#endif +bool vm_is_writable(uint8_t* address, [[maybe_unused]] size_t size) { + return vm_query(address).value_or(VmBasicInfo{}).access.write; +} - if (ip == reinterpret_cast(old_ip)) { - ip = reinterpret_cast(new_ip); - } +bool vm_is_executable(uint8_t* address) { + return vm_query(address).value_or(VmBasicInfo{}).access.execute; +} -#ifdef _M_X64 - ctx->Rip = ip; -#else - ctx->Eip = ip; -#endif +SystemInfo system_info() { + auto page_size = static_cast(sysconf(_SC_PAGESIZE)); + + SystemInfo info{}; + info.page_size = page_size; + info.allocation_granularity = page_size; + info.min_address = reinterpret_cast(0x10000); + info.max_address = reinterpret_cast(1ull << 47); + + return info; } + +void trap_threads([[maybe_unused]] uint8_t* from, [[maybe_unused]] uint8_t* to, [[maybe_unused]] size_t len, + const std::function& run_fn) { + auto from_protect = vm_protect(from, len, VM_ACCESS_RWX).value_or(0); + auto to_protect = vm_protect(to, len, VM_ACCESS_RWX).value_or(0); + run_fn(); + vm_protect(to, len, to_protect); + vm_protect(from, len, from_protect); +} + +void fix_ip([[maybe_unused]] ThreadContext ctx, [[maybe_unused]] uint8_t* old_ip, [[maybe_unused]] uint8_t* new_ip) { +} + } // namespace safetyhook +#endif + // -// Source file: utility.cpp +// Source file: os.windows.cpp // +#include +#include +#include + + +#if SAFETYHOOK_OS_WINDOWS + +#define NOMINMAX #if __has_include() #include #elif __has_include() @@ -1052,37 +1149,113 @@ void fix_ip(ThreadContext thread_ctx, uint8_t* old_ip, uint8_t* new_ip) { namespace safetyhook { -bool is_page_executable(uint8_t* address) { - MEMORY_BASIC_INFORMATION mbi; +std::expected vm_allocate(uint8_t* address, size_t size, VmAccess access) { + DWORD protect = 0; + + if (access == VM_ACCESS_R) { + protect = PAGE_READONLY; + } else if (access == VM_ACCESS_RW) { + protect = PAGE_READWRITE; + } else if (access == VM_ACCESS_RX) { + protect = PAGE_EXECUTE_READ; + } else if (access == VM_ACCESS_RWX) { + protect = PAGE_EXECUTE_READWRITE; + } else { + return std::unexpected{OsError::FAILED_TO_ALLOCATE}; + } - if (VirtualQuery(address, &mbi, sizeof(mbi)) == 0) { - return false; + auto* result = VirtualAlloc(address, size, MEM_COMMIT | MEM_RESERVE, protect); + + if (result == nullptr) { + return std::unexpected{OsError::FAILED_TO_ALLOCATE}; + } + + return static_cast(result); +} + +void vm_free(uint8_t* address) { + VirtualFree(address, 0, MEM_RELEASE); +} + +std::expected vm_protect(uint8_t* address, size_t size, VmAccess access) { + DWORD protect = 0; + + if (access == VM_ACCESS_R) { + protect = PAGE_READONLY; + } else if (access == VM_ACCESS_RW) { + protect = PAGE_READWRITE; + } else if (access == VM_ACCESS_RX) { + protect = PAGE_EXECUTE_READ; + } else if (access == VM_ACCESS_RWX) { + protect = PAGE_EXECUTE_READWRITE; + } else { + return std::unexpected{OsError::FAILED_TO_PROTECT}; } - const auto executable_protect = PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY; + return vm_protect(address, size, protect); +} + +std::expected vm_protect(uint8_t* address, size_t size, uint32_t protect) { + DWORD old_protect = 0; - return (mbi.Protect & executable_protect) != 0; + if (VirtualProtect(address, size, protect, &old_protect) == FALSE) { + return std::unexpected{OsError::FAILED_TO_PROTECT}; + } + + return old_protect; } -bool is_executable(uint8_t* address) { - LPVOID image_base_ptr; +std::expected vm_query(uint8_t* address) { + MEMORY_BASIC_INFORMATION mbi{}; + auto result = VirtualQuery(address, &mbi, sizeof(mbi)); - if (RtlPcToFileHeader(address, &image_base_ptr) == nullptr) { - return is_page_executable(address); + if (result == 0) { + return std::unexpected{OsError::FAILED_TO_QUERY}; + } + + VmAccess access{}; + access.read = (mbi.Protect & (PAGE_READONLY | PAGE_READWRITE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) != 0; + access.write = (mbi.Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE)) != 0; + access.execute = (mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) != 0; + + VmBasicInfo info{}; + info.address = static_cast(mbi.AllocationBase); + info.size = mbi.RegionSize; + info.access = access; + info.is_free = mbi.State == MEM_FREE; + + return info; +} + +bool vm_is_readable(uint8_t* address, size_t size) { + return IsBadReadPtr(address, size) == FALSE; +} + +bool vm_is_writable(uint8_t* address, size_t size) { + return IsBadWritePtr(address, size) == FALSE; +} + +bool vm_is_executable(uint8_t* address) { + // Check if the address is in a valid module allowing us to potentially skip a heavier memory query. + HMODULE image{}; + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(address), &image) || + image == nullptr) { + return vm_query(address).value_or(VmBasicInfo{}).access.execute; } // Just check if the section is executable. - const auto* image_base = reinterpret_cast(image_base_ptr); + const auto* image_base = reinterpret_cast(image); const auto* dos_hdr = reinterpret_cast(image_base); if (dos_hdr->e_magic != IMAGE_DOS_SIGNATURE) { - return is_page_executable(address); + return vm_query(address).value_or(VmBasicInfo{}).access.execute; } const auto* nt_hdr = reinterpret_cast(image_base + dos_hdr->e_lfanew); if (nt_hdr->Signature != IMAGE_NT_SIGNATURE) { - return is_page_executable(address); + return vm_query(address).value_or(VmBasicInfo{}).access.execute; } const auto* section = IMAGE_FIRST_SECTION(nt_hdr); @@ -1094,13 +1267,228 @@ bool is_executable(uint8_t* address) { } } - return is_page_executable(address); + return vm_query(address).value_or(VmBasicInfo{}).access.execute; +} + +SystemInfo system_info() { + SystemInfo info{}; + + SYSTEM_INFO si{}; + GetSystemInfo(&si); + + info.page_size = si.dwPageSize; + info.allocation_granularity = si.dwAllocationGranularity; + info.min_address = static_cast(si.lpMinimumApplicationAddress); + info.max_address = static_cast(si.lpMaximumApplicationAddress); + + return info; +} + +struct TrapInfo { + uint8_t* from_page_start; + uint8_t* from_page_end; + uint8_t* from; + uint8_t* to_page_start; + uint8_t* to_page_end; + uint8_t* to; + size_t len; +}; + +class TrapManager final { +public: + static std::mutex mutex; + static std::unique_ptr instance; + static bool is_destructed; + + TrapManager() { m_trap_veh = AddVectoredExceptionHandler(1, trap_handler); } + ~TrapManager() { + if (m_trap_veh != nullptr) { + RemoveVectoredExceptionHandler(m_trap_veh); + } + is_destructed = true; + } + + TrapInfo* find_trap(uint8_t* address) { + auto search = std::find_if(m_traps.begin(), m_traps.end(), [address](auto& trap) { + return address >= trap.second.from && address < trap.second.from + trap.second.len; + }); + + if (search == m_traps.end()) { + return nullptr; + } + + return &search->second; + } + + TrapInfo* find_trap_page(uint8_t* address) { + auto search = std::find_if(m_traps.begin(), m_traps.end(), [address](auto& trap) { + return address >= trap.second.from_page_start && address < trap.second.from_page_end; + }); + + if (search != m_traps.end()) { + return &search->second; + } + + search = std::find_if(m_traps.begin(), m_traps.end(), [address](auto& trap) { + return address >= trap.second.to_page_start && address < trap.second.to_page_end; + }); + + if (search != m_traps.end()) { + return &search->second; + } + + return nullptr; + } + + void add_trap(uint8_t* from, uint8_t* to, size_t len) { + TrapInfo info{}; + info.from_page_start = align_down(from, 0x1000); + info.from_page_end = align_up(from + len, 0x1000); + info.from = from; + info.to_page_start = align_down(to, 0x1000); + info.to_page_end = align_up(to + len, 0x1000); + info.to = to; + info.len = len; + + m_traps.insert_or_assign(from, std::move(info)); + } + +private: + std::map m_traps; + PVOID m_trap_veh{}; + + static LONG CALLBACK trap_handler(PEXCEPTION_POINTERS exp) { + auto exception_code = exp->ExceptionRecord->ExceptionCode; + + if (exception_code != EXCEPTION_ACCESS_VIOLATION) { + return EXCEPTION_CONTINUE_SEARCH; + } + + std::scoped_lock lock{mutex}; + auto* faulting_address = reinterpret_cast(exp->ExceptionRecord->ExceptionInformation[1]); + auto* trap = instance->find_trap(faulting_address); + + if (trap == nullptr) { + if (instance->find_trap_page(faulting_address) != nullptr) { + return EXCEPTION_CONTINUE_EXECUTION; + } else { + return EXCEPTION_CONTINUE_SEARCH; + } + } + + auto* ctx = exp->ContextRecord; + + for (size_t i = 0; i < trap->len; i++) { + fix_ip(ctx, trap->from + i, trap->to + i); + } + + return EXCEPTION_CONTINUE_EXECUTION; + } +}; + +std::mutex TrapManager::mutex; +std::unique_ptr TrapManager::instance; +bool TrapManager::is_destructed = false; + +void find_me() { +} + +static std::mutex virtual_protect_mutex; + +void trap_threads(uint8_t* from, uint8_t* to, size_t len, const std::function& run_fn) { + MEMORY_BASIC_INFORMATION find_me_mbi{}; + MEMORY_BASIC_INFORMATION from_mbi{}; + MEMORY_BASIC_INFORMATION to_mbi{}; + + VirtualQuery(reinterpret_cast(find_me), &find_me_mbi, sizeof(find_me_mbi)); + VirtualQuery(from, &from_mbi, sizeof(from_mbi)); + VirtualQuery(to, &to_mbi, sizeof(to_mbi)); + + if (to_mbi.State != MEM_COMMIT) { + return; + } + + auto new_protect = PAGE_READWRITE; + + if (from_mbi.AllocationBase == find_me_mbi.AllocationBase || to_mbi.AllocationBase == find_me_mbi.AllocationBase) { + new_protect = PAGE_EXECUTE_READWRITE; + } + + auto si = system_info(); + auto* from_page_start = align_down(from, si.page_size); + auto* from_page_end = align_up(from + len, si.page_size); + auto* vp_start = reinterpret_cast(&VirtualProtect); + auto* vp_end = vp_start + 0x20; + + if (!(from_page_end < vp_start || vp_end < from_page_start)) { + new_protect = PAGE_EXECUTE_READWRITE; + } + + if (!TrapManager::is_destructed) { + std::scoped_lock lock{TrapManager::mutex}; + + if (TrapManager::instance == nullptr) { + TrapManager::instance = std::make_unique(); + } + + TrapManager::instance->add_trap(from, to, len); + } + + // Make sure we aren't working on a different address in the same memory page on a different thread. + std::scoped_lock vp_lock{virtual_protect_mutex}; + + DWORD from_protect; + DWORD to_protect; + + VirtualProtect(from, len, new_protect, &from_protect); + VirtualProtect(to, len, new_protect, &to_protect); + + if (run_fn) { + run_fn(); + } + + VirtualProtect(to, len, to_protect, &to_protect); + VirtualProtect(from, len, from_protect, &from_protect); +} + +void fix_ip(ThreadContext thread_ctx, uint8_t* old_ip, uint8_t* new_ip) { + auto* ctx = reinterpret_cast(thread_ctx); + +#if SAFETYHOOK_ARCH_X86_64 + auto ip = ctx->Rip; +#elif SAFETYHOOK_ARCH_X86_32 + auto ip = ctx->Eip; +#endif + + if (ip == reinterpret_cast(old_ip)) { + ip = reinterpret_cast(new_ip); + } + +#if SAFETYHOOK_ARCH_X86_64 + ctx->Rip = ip; +#elif SAFETYHOOK_ARCH_X86_32 + ctx->Eip = ip; +#endif +} + +} // namespace safetyhook + +#endif + +// +// Source file: utility.cpp +// + + + +namespace safetyhook { +bool is_executable(uint8_t* address) { + return vm_is_executable(address); } UnprotectMemory::~UnprotectMemory() { if (m_address != nullptr) { - DWORD old_protection; - VirtualProtect(m_address, m_size, m_original_protection, &old_protection); + [[maybe_unused]] auto old_protection = vm_protect(m_address, m_size, m_original_protection); } } @@ -1122,13 +1510,13 @@ UnprotectMemory& UnprotectMemory::operator=(UnprotectMemory&& other) noexcept { } std::optional unprotect(uint8_t* address, size_t size) { - DWORD old_protection; + auto old_protection = vm_protect(address, size, VM_ACCESS_RWX); - if (!VirtualProtect(address, size, PAGE_EXECUTE_READWRITE, &old_protection)) { - return {}; + if (!old_protection) { + return std::nullopt; } - return UnprotectMemory{address, size, old_protection}; + return UnprotectMemory{address, size, old_protection.value()}; } } // namespace safetyhook @@ -1137,13 +1525,7 @@ std::optional unprotect(uint8_t* address, size_t size) { // Source file: vmt_hook.cpp // -#if __has_include() -#include -#elif __has_include() -#include -#else -#error "Windows.h not found" -#endif +#include @@ -1188,15 +1570,18 @@ std::expected VmtHook::create(void* object) { const auto original_vmt = *reinterpret_cast(object); hook.m_objects.emplace(object, original_vmt); - // Count the number of virtual method pointers. We start at one to account for the RTTI pointer. - auto num_vmt_entries = 1; + // Count the number of virtual method pointers. We start at VMT_HEADER to account for + // the vtable prefix entries (offset-to-top + RTTI ptr on Itanium, RTTICompleteObjectLocator* on MSVC). + auto num_vmt_entries = VMT_HEADER; for (auto vm = original_vmt; is_executable(*vm); ++vm) { ++num_vmt_entries; } + auto size = num_vmt_entries * sizeof(uint8_t*); + // Allocate memory for the new VMT. - auto allocation = Allocator::global()->allocate(num_vmt_entries * sizeof(uint8_t*)); + auto allocation = Allocator::global()->allocate(size); if (!allocation) { return std::unexpected{Error::bad_allocation(allocation.error())}; @@ -1205,15 +1590,10 @@ std::expected VmtHook::create(void* object) { hook.m_new_vmt_allocation = std::make_shared(std::move(*allocation)); hook.m_new_vmt = reinterpret_cast(hook.m_new_vmt_allocation->data()); - // Copy pointer to RTTI. - hook.m_new_vmt[0] = original_vmt[-1]; - - // Copy virtual method pointers. - for (auto i = 0; i < num_vmt_entries - 1; ++i) { - hook.m_new_vmt[i + 1] = original_vmt[i]; - } + // Copy RTTI header and virtual method pointers. + std::memcpy(hook.m_new_vmt, original_vmt - VMT_HEADER, size); - *reinterpret_cast(object) = &hook.m_new_vmt[1]; + *reinterpret_cast(object) = &hook.m_new_vmt[VMT_HEADER]; return hook; } @@ -1237,7 +1617,7 @@ VmtHook::~VmtHook() { void VmtHook::apply(void* object) { m_objects.emplace(object, *reinterpret_cast(object)); - *reinterpret_cast(object) = &m_new_vmt[1]; + *reinterpret_cast(object) = &m_new_vmt[VMT_HEADER]; } void VmtHook::remove(void* object) { @@ -1249,17 +1629,17 @@ void VmtHook::remove(void* object) { const auto original_vmt = search->second; - execute_while_frozen([&] { - if (IsBadWritePtr(object, sizeof(void*))) { - return; - } + if (!vm_is_writable(reinterpret_cast(object), sizeof(void*))) { + m_objects.erase(search); + return; + } - if (*reinterpret_cast(object) != &m_new_vmt[1]) { - return; - } + if (*reinterpret_cast(object) != &m_new_vmt[VMT_HEADER]) { + m_objects.erase(search); + return; + } - *reinterpret_cast(object) = original_vmt; - }); + *reinterpret_cast(object) = original_vmt; m_objects.erase(search); } @@ -1269,19 +1649,17 @@ void VmtHook::reset() { } void VmtHook::destroy() { - execute_while_frozen([this] { - for (const auto [object, original_vmt] : m_objects) { - if (IsBadWritePtr(object, sizeof(void*))) { - return; - } - - if (*reinterpret_cast(object) != &m_new_vmt[1]) { - return; - } + for (const auto [object, original_vmt] : m_objects) { + if (!vm_is_writable(reinterpret_cast(object), sizeof(void*))) { + continue; + } - *reinterpret_cast(object) = original_vmt; + if (*reinterpret_cast(object) != &m_new_vmt[VMT_HEADER]) { + continue; } - }); + + *reinterpret_cast(object) = original_vmt; + } m_objects.clear(); m_new_vmt_allocation.reset(); diff --git a/safetyhook/safetyhook.hpp b/safetyhook/safetyhook.hpp index 8dc89a424..f7d30a491 100644 --- a/safetyhook/safetyhook.hpp +++ b/safetyhook/safetyhook.hpp @@ -8,6 +8,198 @@ #pragma once +// +// Header: safetyhook/os.hpp +// +// Include stack: +// - safetyhook.hpp +// + +// This is the OS abstraction layer. +#pragma once + +#ifndef SAFETYHOOK_USE_CXXMODULES +#include +#include +#include +#else +import std.compat; +#endif + + +// +// Header: safetyhook/common.hpp +// +// Include stack: +// - safetyhook.hpp +// - safetyhook/os.hpp +// + +#pragma once + +#if defined(_MSC_VER) +#define SAFETYHOOK_COMPILER_MSVC 1 +#define SAFETYHOOK_COMPILER_GCC 0 +#define SAFETYHOOK_COMPILER_CLANG 0 +#elif defined(__clang__) +#define SAFETYHOOK_COMPILER_MSVC 0 +#define SAFETYHOOK_COMPILER_GCC 0 +#define SAFETYHOOK_COMPILER_CLANG 1 +#elif defined(__GNUC__) +#define SAFETYHOOK_COMPILER_MSVC 0 +#define SAFETYHOOK_COMPILER_GCC 1 +#define SAFETYHOOK_COMPILER_CLANG 0 +#else +#error "Unsupported compiler" +#endif + +#if defined(_MSC_VER) +#define SAFETYHOOK_ABI_MSVC 1 +#define SAFETYHOOK_ABI_ITANIUM 0 +#else +#define SAFETYHOOK_ABI_MSVC 0 +#define SAFETYHOOK_ABI_ITANIUM 1 +#endif + +#if SAFETYHOOK_COMPILER_MSVC +#if defined(_M_IX86) +#define SAFETYHOOK_ARCH_X86_32 1 +#define SAFETYHOOK_ARCH_X86_64 0 +#elif defined(_M_X64) +#define SAFETYHOOK_ARCH_X86_32 0 +#define SAFETYHOOK_ARCH_X86_64 1 +#else +#error "Unsupported architecture" +#endif +#elif SAFETYHOOK_COMPILER_GCC || SAFETYHOOK_COMPILER_CLANG +#if defined(__i386__) +#define SAFETYHOOK_ARCH_X86_32 1 +#define SAFETYHOOK_ARCH_X86_64 0 +#elif defined(__x86_64__) +#define SAFETYHOOK_ARCH_X86_32 0 +#define SAFETYHOOK_ARCH_X86_64 1 +#else +#error "Unsupported architecture" +#endif +#endif + +#if defined(_WIN32) +#define SAFETYHOOK_OS_WINDOWS 1 +#define SAFETYHOOK_OS_LINUX 0 +#elif defined(__linux__) +#define SAFETYHOOK_OS_WINDOWS 0 +#define SAFETYHOOK_OS_LINUX 1 +#else +#error "Unsupported OS" +#endif + +#if SAFETYHOOK_OS_WINDOWS +#if SAFETYHOOK_COMPILER_MSVC +#define SAFETYHOOK_CCALL __cdecl +#define SAFETYHOOK_STDCALL __stdcall +#define SAFETYHOOK_FASTCALL __fastcall +#define SAFETYHOOK_THISCALL __thiscall +#elif SAFETYHOOK_COMPILER_GCC || SAFETYHOOK_COMPILER_CLANG +#define SAFETYHOOK_CCALL __attribute__((cdecl)) +#define SAFETYHOOK_STDCALL __attribute__((stdcall)) +#define SAFETYHOOK_FASTCALL __attribute__((fastcall)) +#define SAFETYHOOK_THISCALL __attribute__((thiscall)) +#endif +#else +#define SAFETYHOOK_CCALL +#define SAFETYHOOK_STDCALL +#define SAFETYHOOK_FASTCALL +#define SAFETYHOOK_THISCALL +#endif + +#if SAFETYHOOK_COMPILER_MSVC +#define SAFETYHOOK_NOINLINE __declspec(noinline) +#elif SAFETYHOOK_COMPILER_GCC || SAFETYHOOK_COMPILER_CLANG +#define SAFETYHOOK_NOINLINE __attribute__((noinline)) +#endif + +#if SAFETYHOOK_COMPILER_MSVC +#define SAFETYHOOK_DLLEXPORT __declspec(dllexport) +#define SAFETYHOOK_DLLIMPORT __declspec(dllimport) +#elif SAFETYHOOK_COMPILER_GCC || SAFETYHOOK_COMPILER_CLANG +#define SAFETYHOOK_DLLEXPORT __attribute__((visibility("default"))) +#define SAFETYHOOK_DLLIMPORT +#endif + +#if SAFETYHOOK_SHARED_LIB && SAFETYHOOK_BUILDING +#define SAFETYHOOK_API SAFETYHOOK_DLLEXPORT +#elif SAFETYHOOK_SHARED_LIB +#define SAFETYHOOK_API SAFETYHOOK_DLLIMPORT +#else +#define SAFETYHOOK_API +#endif + +namespace safetyhook { + +enum class OsError { + FAILED_TO_ALLOCATE, + FAILED_TO_PROTECT, + FAILED_TO_QUERY, + FAILED_TO_GET_NEXT_THREAD, + FAILED_TO_GET_THREAD_CONTEXT, + FAILED_TO_SET_THREAD_CONTEXT, + FAILED_TO_FREEZE_THREAD, + FAILED_TO_UNFREEZE_THREAD, + FAILED_TO_GET_THREAD_ID, +}; + +struct VmAccess { + bool read : 1; + bool write : 1; + bool execute : 1; + + constexpr bool operator==(const VmAccess& other) const { + return read == other.read && write == other.write && execute == other.execute; + } +}; + +inline constexpr VmAccess VM_ACCESS_R{true, false, false}; +inline constexpr VmAccess VM_ACCESS_RW{true, true, false}; +inline constexpr VmAccess VM_ACCESS_RX{true, false, true}; +inline constexpr VmAccess VM_ACCESS_RWX{true, true, true}; + +struct VmBasicInfo { + uint8_t* address; + size_t size; + VmAccess access; + bool is_free; +}; + +std::expected SAFETYHOOK_API vm_allocate(uint8_t* address, size_t size, VmAccess access); +void SAFETYHOOK_API vm_free(uint8_t* address); +std::expected SAFETYHOOK_API vm_protect(uint8_t* address, size_t size, VmAccess access); +std::expected SAFETYHOOK_API vm_protect(uint8_t* address, size_t size, uint32_t access); +std::expected SAFETYHOOK_API vm_query(uint8_t* address); +bool SAFETYHOOK_API vm_is_readable(uint8_t* address, size_t size); +bool SAFETYHOOK_API vm_is_writable(uint8_t* address, size_t size); +bool SAFETYHOOK_API vm_is_executable(uint8_t* address); + +struct SystemInfo { + uint32_t page_size; + uint32_t allocation_granularity; + uint8_t* min_address; + uint8_t* max_address; +}; + +SystemInfo SAFETYHOOK_API system_info(); + +using ThreadContext = void*; + +void SAFETYHOOK_API trap_threads(uint8_t* from, uint8_t* to, size_t len, const std::function& run_fn); + +/// @brief Will modify the context of a thread's IP to point to a new address if its IP is at the old address. +/// @param ctx The thread context to modify. +/// @param old_ip The old IP address. +/// @param new_ip The new IP address. +void SAFETYHOOK_API fix_ip(ThreadContext ctx, uint8_t* old_ip, uint8_t* new_ip); + +} // namespace safetyhook + // // Header: safetyhook/easy.hpp // @@ -34,13 +226,16 @@ #pragma once +#ifndef SAFETYHOOK_USE_CXXMODULES #include #include #include #include #include #include -#include +#else +import std.compat; +#endif // @@ -57,17 +252,22 @@ #pragma once +#ifndef SAFETYHOOK_USE_CXXMODULES #include #include #include #include #include +#else +import std.compat; +#endif + namespace safetyhook { class Allocator; /// @brief A memory allocation. -class Allocation final { +class SAFETYHOOK_API Allocation final { public: Allocation() = default; Allocation(const Allocation&) = delete; @@ -86,7 +286,7 @@ class Allocation final { /// @brief Returns the address of the allocation. /// @return The address of the allocation. - [[nodiscard]] uintptr_t address() const noexcept { return (uintptr_t)m_address; } + [[nodiscard]] uintptr_t address() const noexcept { return reinterpret_cast(m_address); } /// @brief Returns the size of the allocation. /// @return The size of the allocation. @@ -108,7 +308,7 @@ class Allocation final { }; /// @brief Allocates memory near target addresses. -class Allocator final : public std::enable_shared_from_this { +class SAFETYHOOK_API Allocator final : public std::enable_shared_from_this { public: /// @brief Returns the global Allocator. /// @return The global Allocator. @@ -191,21 +391,32 @@ class Allocator final : public std::enable_shared_from_this { #pragma once +#ifndef SAFETYHOOK_USE_CXXMODULES #include #include +#include #include +#else +import std.compat; +#endif + namespace safetyhook { template constexpr void store(uint8_t* address, const T& value) { std::copy_n(reinterpret_cast(&value), sizeof(T), address); } -template -concept FnPtr = requires(T f) { std::is_pointer_v&& std::is_function_v>; }; +template constexpr T address_cast(U address) { + if constexpr (std::is_integral_v && std::is_integral_v) { + return static_cast(address); + } else { + return reinterpret_cast(address); + } +} -bool is_executable(uint8_t* address); +bool SAFETYHOOK_API is_executable(uint8_t* address); -class UnprotectMemory { +class SAFETYHOOK_API UnprotectMemory { public: UnprotectMemory() = delete; ~UnprotectMemory(); @@ -215,7 +426,7 @@ class UnprotectMemory { UnprotectMemory& operator=(UnprotectMemory&& other) noexcept; private: - friend std::optional unprotect(uint8_t*, size_t); + friend std::optional SAFETYHOOK_API unprotect(uint8_t*, size_t); UnprotectMemory(uint8_t* address, size_t size, uint32_t original_protection) : m_address{address}, m_size{size}, m_original_protection{original_protection} {} @@ -225,12 +436,24 @@ class UnprotectMemory { uint32_t m_original_protection{}; }; -[[nodiscard]] std::optional unprotect(uint8_t* address, size_t size); +[[nodiscard]] std::optional SAFETYHOOK_API unprotect(uint8_t* address, size_t size); + +template constexpr T align_up(T address, size_t align) { + const auto unaligned_address = address_cast(address); + const auto aligned_address = (unaligned_address + align - 1) & ~(align - 1); + return address_cast(aligned_address); +} + +template constexpr T align_down(T address, size_t align) { + const auto unaligned_address = address_cast(address); + const auto aligned_address = unaligned_address & ~(align - 1); + return address_cast(aligned_address); +} } // namespace safetyhook namespace safetyhook { /// @brief An inline hook. -class InlineHook final { +class SAFETYHOOK_API InlineHook final { public: /// @brief Error type for InlineHook. struct Error { @@ -255,84 +478,122 @@ class InlineHook final { /// @param err The Allocator::Error that failed. /// @return The new BAD_ALLOCATION error. [[nodiscard]] static Error bad_allocation(Allocator::Error err) { - return {.type = BAD_ALLOCATION, .allocator_error = err}; + Error error{}; + error.type = BAD_ALLOCATION; + error.allocator_error = err; + return error; } /// @brief Create a FAILED_TO_DECODE_INSTRUCTION error. /// @param ip The IP of the problematic instruction. /// @return The new FAILED_TO_DECODE_INSTRUCTION error. [[nodiscard]] static Error failed_to_decode_instruction(uint8_t* ip) { - return {.type = FAILED_TO_DECODE_INSTRUCTION, .ip = ip}; + Error error{}; + error.type = FAILED_TO_DECODE_INSTRUCTION; + error.ip = ip; + return error; } /// @brief Create a SHORT_JUMP_IN_TRAMPOLINE error. /// @param ip The IP of the problematic instruction. /// @return The new SHORT_JUMP_IN_TRAMPOLINE error. [[nodiscard]] static Error short_jump_in_trampoline(uint8_t* ip) { - return {.type = SHORT_JUMP_IN_TRAMPOLINE, .ip = ip}; + Error error{}; + error.type = SHORT_JUMP_IN_TRAMPOLINE; + error.ip = ip; + return error; } /// @brief Create a IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE error. /// @param ip The IP of the problematic instruction. /// @return The new IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE error. [[nodiscard]] static Error ip_relative_instruction_out_of_range(uint8_t* ip) { - return {.type = IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE, .ip = ip}; + Error error{}; + error.type = IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE; + error.ip = ip; + return error; } /// @brief Create a UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE error. /// @param ip The IP of the problematic instruction. /// @return The new UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE error. [[nodiscard]] static Error unsupported_instruction_in_trampoline(uint8_t* ip) { - return {.type = UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE, .ip = ip}; + Error error{}; + error.type = UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE; + error.ip = ip; + return error; } /// @brief Create a FAILED_TO_UNPROTECT error. /// @param ip The IP of the problematic instruction. /// @return The new FAILED_TO_UNPROTECT error. - [[nodiscard]] static Error failed_to_unprotect(uint8_t* ip) { return {.type = FAILED_TO_UNPROTECT, .ip = ip}; } + [[nodiscard]] static Error failed_to_unprotect(uint8_t* ip) { + Error error{}; + error.type = FAILED_TO_UNPROTECT; + error.ip = ip; + return error; + } /// @brief Create a NOT_ENOUGH_SPACE error. /// @param ip The IP of the problematic instruction. /// @return The new NOT_ENOUGH_SPACE error. - [[nodiscard]] static Error not_enough_space(uint8_t* ip) { return {.type = NOT_ENOUGH_SPACE, .ip = ip}; } + [[nodiscard]] static Error not_enough_space(uint8_t* ip) { + Error error{}; + error.type = NOT_ENOUGH_SPACE; + error.ip = ip; + return error; + } + }; + + /// @brief Flags for InlineHook. + enum Flags : int { + Default = 0, ///< Default flags. + StartDisabled = 1 << 0, ///< Start the hook disabled. }; /// @brief Create an inline hook. /// @param target The address of the function to hook. /// @param destination The destination address. + /// @param flags The flags to use. /// @return The InlineHook or an InlineHook::Error if an error occurred. /// @note This will use the default global Allocator. /// @note If you don't care about error handling, use the easy API (safetyhook::create_inline). - [[nodiscard]] static std::expected create(void* target, void* destination); + [[nodiscard]] static std::expected create( + void* target, void* destination, Flags flags = Default); /// @brief Create an inline hook. /// @param target The address of the function to hook. /// @param destination The destination address. + /// @param flags The flags to use. /// @return The InlineHook or an InlineHook::Error if an error occurred. /// @note This will use the default global Allocator. /// @note If you don't care about error handling, use the easy API (safetyhook::create_inline). - [[nodiscard]] static std::expected create(FnPtr auto target, FnPtr auto destination) { - return create(reinterpret_cast(target), reinterpret_cast(destination)); + template + [[nodiscard]] static std::expected create(T target, U destination, Flags flags = Default) { + return create(reinterpret_cast(target), reinterpret_cast(destination), flags); } /// @brief Create an inline hook with a given Allocator. /// @param allocator The allocator to use. /// @param target The address of the function to hook. /// @param destination The destination address. + /// @param flags The flags to use. /// @return The InlineHook or an InlineHook::Error if an error occurred. /// @note If you don't care about error handling, use the easy API (safetyhook::create_inline). [[nodiscard]] static std::expected create( - const std::shared_ptr& allocator, void* target, void* destination); + const std::shared_ptr& allocator, void* target, void* destination, Flags flags = Default); /// @brief Create an inline hook with a given Allocator. /// @param allocator The allocator to use. /// @param target The address of the function to hook. /// @param destination The destination address. + /// @param flags The flags to use. /// @return The InlineHook or an InlineHook::Error if an error occurred. /// @note If you don't care about error handling, use the easy API (safetyhook::create_inline). + template [[nodiscard]] static std::expected create( - const std::shared_ptr& allocator, FnPtr auto target, FnPtr auto destination) { - return create(allocator, reinterpret_cast(target), reinterpret_cast(destination)); + const std::shared_ptr& allocator, T target, U destination, Flags flags = Default) { + return create(allocator, reinterpret_cast(target), reinterpret_cast(destination), flags); } InlineHook() = default; @@ -399,7 +660,7 @@ class InlineHook final { /// @note This function will use the __cdecl calling convention. template RetT ccall(Args... args) { std::scoped_lock lock{m_mutex}; - return m_trampoline ? original()(args...) : RetT(); + return m_trampoline ? original()(args...) : RetT(); } /// @brief Calls the original function. @@ -410,7 +671,7 @@ class InlineHook final { /// @note This function will use the __thiscall calling convention. template RetT thiscall(Args... args) { std::scoped_lock lock{m_mutex}; - return m_trampoline ? original()(args...) : RetT(); + return m_trampoline ? original()(args...) : RetT(); } /// @brief Calls the original function. @@ -421,7 +682,7 @@ class InlineHook final { /// @note This function will use the __stdcall calling convention. template RetT stdcall(Args... args) { std::scoped_lock lock{m_mutex}; - return m_trampoline ? original()(args...) : RetT(); + return m_trampoline ? original()(args...) : RetT(); } /// @brief Calls the original function. @@ -432,7 +693,7 @@ class InlineHook final { /// @note This function will use the __fastcall calling convention. template RetT fastcall(Args... args) { std::scoped_lock lock{m_mutex}; - return m_trampoline ? original()(args...) : RetT(); + return m_trampoline ? original()(args...) : RetT(); } /// @brief Calls the original function. @@ -456,7 +717,7 @@ class InlineHook final { /// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook /// safety or are worried about the performance cost of locking the mutex. template RetT unsafe_ccall(Args... args) { - return original()(args...); + return original()(args...); } /// @brief Calls the original function. @@ -468,7 +729,7 @@ class InlineHook final { /// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook /// safety or are worried about the performance cost of locking the mutex. template RetT unsafe_thiscall(Args... args) { - return original()(args...); + return original()(args...); } /// @brief Calls the original function. @@ -480,7 +741,7 @@ class InlineHook final { /// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook /// safety or are worried about the performance cost of locking the mutex. template RetT unsafe_stdcall(Args... args) { - return original()(args...); + return original()(args...); } /// @brief Calls the original function. @@ -492,24 +753,41 @@ class InlineHook final { /// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook /// safety or are worried about the performance cost of locking the mutex. template RetT unsafe_fastcall(Args... args) { - return original()(args...); + return original()(args...); } + /// @brief Enable the hook. + [[nodiscard]] std::expected enable(); + + /// @brief Disable the hook. + [[nodiscard]] std::expected disable(); + + /// @brief Check if the hook is enabled. + [[nodiscard]] bool enabled() const { return m_enabled; } + private: friend class MidHook; + enum class Type { + Unset, + E9, + FF, + }; + uint8_t* m_target{}; uint8_t* m_destination{}; Allocation m_trampoline{}; std::vector m_original_bytes{}; uintptr_t m_trampoline_size{}; std::recursive_mutex m_mutex{}; + bool m_enabled{}; + Type m_type{Type::Unset}; std::expected setup( const std::shared_ptr& allocator, uint8_t* target, uint8_t* destination); std::expected e9_hook(const std::shared_ptr& allocator); -#ifdef _M_X64 +#if SAFETYHOOK_ARCH_X86_64 std::expected ff_hook(const std::shared_ptr& allocator); #endif @@ -530,8 +808,12 @@ class InlineHook final { #pragma once +#ifndef SAFETYHOOK_USE_CXXMODULES #include #include +#else +import std.compat; +#endif // @@ -548,7 +830,12 @@ class InlineHook final { #pragma once +#ifndef SAFETYHOOK_USE_CXXMODULES #include +#else +import std.compat; +#endif + namespace safetyhook { union Xmm { @@ -587,9 +874,9 @@ struct Context32 { /// to the registers at the moment the hook is called. /// @note The structure is different depending on architecture. /// @note The structure only provides access to integer registers. -#ifdef _M_X64 +#if SAFETYHOOK_ARCH_X86_64 using Context = Context64; -#else +#elif SAFETYHOOK_ARCH_X86_32 using Context = Context32; #endif @@ -601,7 +888,7 @@ namespace safetyhook { using MidHookFn = void (*)(Context& ctx); /// @brief A mid function hook. -class MidHook final { +class SAFETYHOOK_API MidHook final { public: /// @brief Error type for MidHook. struct Error { @@ -621,54 +908,74 @@ class MidHook final { /// @param err The Allocator::Error that failed. /// @return The new BAD_ALLOCATION error. [[nodiscard]] static Error bad_allocation(Allocator::Error err) { - return {.type = BAD_ALLOCATION, .allocator_error = err}; + Error error{}; + error.type = BAD_ALLOCATION; + error.allocator_error = err; + return error; } /// @brief Create a BAD_INLINE_HOOK error. /// @param err The InlineHook::Error that failed. /// @return The new BAD_INLINE_HOOK error. [[nodiscard]] static Error bad_inline_hook(InlineHook::Error err) { - return {.type = BAD_INLINE_HOOK, .inline_hook_error = err}; + Error error{}; + error.type = BAD_INLINE_HOOK; + error.inline_hook_error = err; + return error; } }; + /// @brief Flags for MidHook. + enum Flags : int { + Default = 0, ///< Default flags. + StartDisabled = 1, ///< Start the hook disabled. + }; + /// @brief Creates a new MidHook object. /// @param target The address of the function to hook. /// @param destination_fn The destination function. + /// @param flags The flags to use. /// @return The MidHook object or a MidHook::Error if an error occurred. /// @note This will use the default global Allocator. /// @note If you don't care about error handling, use the easy API (safetyhook::create_mid). - [[nodiscard]] static std::expected create(void* target, MidHookFn destination_fn); + [[nodiscard]] static std::expected create( + void* target, MidHookFn destination_fn, Flags flags = Default); /// @brief Creates a new MidHook object. /// @param target The address of the function to hook. /// @param destination_fn The destination function. + /// @param flags The flags to use. /// @return The MidHook object or a MidHook::Error if an error occurred. /// @note This will use the default global Allocator. /// @note If you don't care about error handling, use the easy API (safetyhook::create_mid). - [[nodiscard]] static std::expected create(FnPtr auto target, MidHookFn destination_fn) { - return create(reinterpret_cast(target), destination_fn); + template + [[nodiscard]] static std::expected create( + T target, MidHookFn destination_fn, Flags flags = Default) { + return create(reinterpret_cast(target), destination_fn, flags); } /// @brief Creates a new MidHook object with a given Allocator. /// @param allocator The Allocator to use. /// @param target The address of the function to hook. /// @param destination_fn The destination function. + /// @param flags The flags to use. /// @return The MidHook object or a MidHook::Error if an error occurred. /// @note If you don't care about error handling, use the easy API (safetyhook::create_mid). [[nodiscard]] static std::expected create( - const std::shared_ptr& allocator, void* target, MidHookFn destination_fn); + const std::shared_ptr& allocator, void* target, MidHookFn destination_fn, Flags flags = Default); /// @brief Creates a new MidHook object with a given Allocator. /// @tparam T The type of the function to hook. /// @param allocator The Allocator to use. /// @param target The address of the function to hook. /// @param destination_fn The destination function. + /// @param flags The flags to use. /// @return The MidHook object or a MidHook::Error if an error occurred. /// @note If you don't care about error handling, use the easy API (safetyhook::create_mid). + template [[nodiscard]] static std::expected create( - const std::shared_ptr& allocator, FnPtr auto target, MidHookFn destination_fn) { - return create(allocator, reinterpret_cast(target), destination_fn); + const std::shared_ptr& allocator, T target, MidHookFn destination_fn, Flags flags = Default) { + return create(allocator, reinterpret_cast(target), destination_fn, flags); } MidHook() = default; @@ -703,6 +1010,15 @@ class MidHook final { /// @return true if the hook is valid, false otherwise. explicit operator bool() const { return static_cast(m_stub); } + /// @brief Enable the hook. + [[nodiscard]] std::expected enable(); + + /// @brief Disable the hook. + [[nodiscard]] std::expected disable(); + + /// @brief Check if the hook is enabled. + [[nodiscard]] bool enabled() const { return m_hook.enabled(); } + private: InlineHook m_hook{}; uint8_t* m_target{}; @@ -727,14 +1043,28 @@ class MidHook final { #pragma once +#ifndef SAFETYHOOK_USE_CXXMODULES #include #include #include +#else +import std.compat; +#endif namespace safetyhook { +/// @brief Number of ABI-specific non-function entries preceding the first virtual function pointer +/// in the vtable layout for the current compiler/ABI. +#if SAFETYHOOK_ABI_MSVC +constexpr size_t VMT_HEADER = 1; // RTTICompleteObjectLocator* +#elif SAFETYHOOK_ABI_ITANIUM +constexpr size_t VMT_HEADER = 2; // offset-to-top + RTTI ptr +#else +constexpr size_t VMT_HEADER = 0; +#endif + /// @brief A hook class that allows for hooking a single method in a VMT. -class VmHook final { +class SAFETYHOOK_API VmHook final { public: VmHook() = default; VmHook(const VmHook&) = delete; @@ -765,7 +1095,7 @@ class VmHook final { /// @param args The arguments to pass to the method. /// @return The return value of the method. template RetT ccall(Args... args) { - return original()(args...); + return original()(args...); } /// @brief Calls the original method with the __thiscall calling convention. @@ -774,7 +1104,7 @@ class VmHook final { /// @param args The arguments to pass to the method. /// @return The return value of the method. template RetT thiscall(Args... args) { - return original()(args...); + return original()(args...); } /// @brief Calls the original method with the __stdcall calling convention. @@ -783,7 +1113,7 @@ class VmHook final { /// @param args The arguments to pass to the method. /// @return The return value of the method. template RetT stdcall(Args... args) { - return original()(args...); + return original()(args...); } /// @brief Calls the original method with the __fastcall calling convention. @@ -792,7 +1122,7 @@ class VmHook final { /// @param args The arguments to pass to the method. /// @return The return value of the method. template RetT fastcall(Args... args) { - return original()(args...); + return original()(args...); } private: @@ -809,7 +1139,7 @@ class VmHook final { }; /// @brief A hook class that copies an entire VMT for a given object and replaces it. -class VmtHook final { +class SAFETYHOOK_API VmtHook final { public: /// @brief Error type for VmtHook. struct Error { @@ -827,7 +1157,10 @@ class VmtHook final { /// @param err The Allocator::Error that failed. /// @return The new BAD_ALLOCATION error. [[nodiscard]] static Error bad_allocation(Allocator::Error err) { - return {.type = BAD_ALLOCATION, .allocator_error = err}; + Error error{}; + error.type = BAD_ALLOCATION; + error.allocator_error = err; + return error; } }; @@ -858,10 +1191,10 @@ class VmtHook final { /// @brief Hooks a method in the VMT. /// @param index The index of the method to hook. /// @param new_function The new function to use. - [[nodiscard]] std::expected hook_method(size_t index, FnPtr auto new_function) { + template [[nodiscard]] std::expected hook_method(size_t index, T new_function) { VmHook hook{}; - ++index; // Skip RTTI pointer. + index += VMT_HEADER; // Skip RTTI header. hook.m_original_vm = m_new_vmt[index]; store(reinterpret_cast(&hook.m_new_vm), new_function); hook.m_vmt_entry = &m_new_vmt[index]; @@ -887,42 +1220,50 @@ namespace safetyhook { /// @brief Easy to use API for creating an InlineHook. /// @param target The address of the function to hook. /// @param destination The address of the destination function. +/// @param flags The flags to use. /// @return The InlineHook object. -[[nodiscard]] InlineHook create_inline(void* target, void* destination); +[[nodiscard]] InlineHook SAFETYHOOK_API create_inline( + void* target, void* destination, InlineHook::Flags flags = InlineHook::Default); /// @brief Easy to use API for creating an InlineHook. /// @param target The address of the function to hook. /// @param destination The address of the destination function. +/// @param flags The flags to use. /// @return The InlineHook object. -[[nodiscard]] InlineHook create_inline(FnPtr auto target, FnPtr auto destination) { - return create_inline(reinterpret_cast(target), reinterpret_cast(destination)); +template +[[nodiscard]] InlineHook create_inline(T target, U destination, InlineHook::Flags flags = InlineHook::Default) { + return create_inline(reinterpret_cast(target), reinterpret_cast(destination), flags); } /// @brief Easy to use API for creating a MidHook. /// @param target the address of the function to hook. /// @param destination The destination function. +/// @param flags The flags to use. /// @return The MidHook object. -[[nodiscard]] MidHook create_mid(void* target, MidHookFn destination); +[[nodiscard]] MidHook SAFETYHOOK_API create_mid( + void* target, MidHookFn destination, MidHook::Flags flags = MidHook::Default); /// @brief Easy to use API for creating a MidHook. /// @param target the address of the function to hook. /// @param destination The destination function. +/// @param flags The flags to use. /// @return The MidHook object. -[[nodiscard]] MidHook create_mid(FnPtr auto target, MidHookFn destination) { - return create_mid(reinterpret_cast(target), destination); +template +[[nodiscard]] MidHook create_mid(T target, MidHookFn destination, MidHook::Flags flags = MidHook::Default) { + return create_mid(reinterpret_cast(target), destination, flags); } /// @brief Easy to use API for creating a VmtHook. /// @param object The object to hook. /// @return The VmtHook object. -[[nodiscard]] VmtHook create_vmt(void* object); +[[nodiscard]] VmtHook SAFETYHOOK_API create_vmt(void* object); /// @brief Easy to use API for creating a VmHook. /// @param vmt The VmtHook to use to create the VmHook. /// @param index The index of the method to hook. /// @param destination The destination function. /// @return The VmHook object. -[[nodiscard]] VmHook create_vm(VmtHook& vmt, size_t index, FnPtr auto destination) { +template [[nodiscard]] VmHook create_vm(VmtHook& vmt, size_t index, T destination) { if (auto hook = vmt.hook_method(index, destination)) { return std::move(*hook); } else { @@ -932,43 +1273,6 @@ namespace safetyhook { } // namespace safetyhook -// -// Header: safetyhook/thread_freezer.hpp -// -// Include stack: -// - safetyhook.hpp -// - -/// @file safetyhook/thread_freezer.hpp -/// @brief A class for freezing all threads in the process. - -#pragma once - -#include -#include - -namespace safetyhook { -using ThreadId = uint32_t; -using ThreadHandle = void*; -using ThreadContext = void*; - -/// @brief Executes a function while all other threads are frozen. Also allows for visiting each frozen thread and -/// modifying it's context. -/// @param run_fn The function to run while all other threads are frozen. -/// @param visit_fn The function that will be called for each frozen thread. -/// @note The visit function will be called in the order that the threads were frozen. -/// @note The visit function will be called before the run function. -/// @note Keep the logic inside run_fn and visit_fn as simple as possible to avoid deadlocks. -void execute_while_frozen(const std::function& run_fn, - const std::function& visit_fn = {}); - -/// @brief Will modify the context of a thread's IP to point to a new address if its IP is at the old address. -/// @param ctx The thread context to modify. -/// @param old_ip The old IP address. -/// @param new_ip The new IP address. -void fix_ip(ThreadContext ctx, uint8_t* old_ip, uint8_t* new_ip); -} // namespace safetyhook - using SafetyHookContext = safetyhook::Context; using SafetyHookInline = safetyhook::InlineHook; using SafetyHookMid = safetyhook::MidHook; diff --git a/shared/bass/bass.dll b/shared/bass/bass.dll index 437a0b63e..6eaec0484 100644 Binary files a/shared/bass/bass.dll and b/shared/bass/bass.dll differ diff --git a/shared/bass/bass.h b/shared/bass/bass.h index b62681082..73dffde8e 100644 --- a/shared/bass/bass.h +++ b/shared/bass/bass.h @@ -1,6 +1,6 @@ /* BASS 2.4 C/C++ header file - Copyright (c) 1999-2021 Un4seen Developments Ltd. + Copyright (c) 1999-2025 Un4seen Developments Ltd. See the BASS.CHM file for more detailed documentation */ @@ -76,6 +76,7 @@ typedef DWORD HPLUGIN; // plugin handle #define BASS_ERROR_START 9 // BASS_Start has not been successfully called #define BASS_ERROR_SSL 10 // SSL/HTTPS support isn't available #define BASS_ERROR_REINIT 11 // device needs to be reinitialized +#define BASS_ERROR_TRACK 13 // invalid track number #define BASS_ERROR_ALREADY 14 // already initialized/paused/whatever #define BASS_ERROR_NOTAUDIO 17 // file does not contain audio #define BASS_ERROR_NOCHAN 18 // can't get a free channel @@ -88,7 +89,7 @@ typedef DWORD HPLUGIN; // plugin handle #define BASS_ERROR_FREQ 25 // illegal sample rate #define BASS_ERROR_NOTFILE 27 // the stream is not a file stream #define BASS_ERROR_NOHW 29 // no hardware voices available -#define BASS_ERROR_EMPTY 31 // the MOD music has no sequence data +#define BASS_ERROR_EMPTY 31 // the file has no sample data #define BASS_ERROR_NONET 32 // no internet connection could be opened #define BASS_ERROR_CREATE 33 // couldn't create the file #define BASS_ERROR_NOFX 34 // effects are not available @@ -104,6 +105,9 @@ typedef DWORD HPLUGIN; // plugin handle #define BASS_ERROR_BUSY 46 // the device is busy #define BASS_ERROR_UNSTREAMABLE 47 // unstreamable file #define BASS_ERROR_PROTOCOL 48 // unsupported protocol +#define BASS_ERROR_DENIED 49 // access denied +#define BASS_ERROR_FREEING 50 // being freed +#define BASS_ERROR_CANCEL 51 // cancelled #define BASS_ERROR_UNKNOWN -1 // some other mystery problem // BASS_SetConfig options @@ -128,7 +132,6 @@ typedef DWORD HPLUGIN; // plugin handle #define BASS_CONFIG_UPDATETHREADS 24 #define BASS_CONFIG_DEV_BUFFER 27 #define BASS_CONFIG_REC_LOOPBACK 28 -#define BASS_CONFIG_VISTA_TRUEPOS 30 #define BASS_CONFIG_IOS_SESSION 34 #define BASS_CONFIG_IOS_MIXAUDIO 34 #define BASS_CONFIG_DEV_DEFAULT 36 @@ -142,7 +145,8 @@ typedef DWORD HPLUGIN; // plugin handle #define BASS_CONFIG_SRC_SAMPLE 44 #define BASS_CONFIG_ASYNCFILE_BUFFER 45 #define BASS_CONFIG_OGG_PRESCAN 47 -#define BASS_CONFIG_MF_VIDEO 48 +#define BASS_CONFIG_VIDEO 48 +#define BASS_CONFIG_MF_VIDEO BASS_CONFIG_VIDEO #define BASS_CONFIG_AIRPLAY 49 #define BASS_CONFIG_DEV_NONSTOP 50 #define BASS_CONFIG_IOS_NOCATEGORY 51 @@ -158,15 +162,23 @@ typedef DWORD HPLUGIN; // plugin handle #define BASS_CONFIG_REC_WASAPI 66 #define BASS_CONFIG_ANDROID_AAUDIO 67 #define BASS_CONFIG_SAMPLE_ONEHANDLE 69 -#define BASS_CONFIG_DEV_TIMEOUT 70 #define BASS_CONFIG_NET_META 71 #define BASS_CONFIG_NET_RESTRATE 72 +#define BASS_CONFIG_REC_DEFAULT 73 +#define BASS_CONFIG_NORAMP 74 +#define BASS_CONFIG_NOSOUND_MAXDELAY 76 +#define BASS_CONFIG_STACKALLOC 79 +#define BASS_CONFIG_DOWNMIX 80 // BASS_SetConfigPtr options #define BASS_CONFIG_NET_AGENT 16 #define BASS_CONFIG_NET_PROXY 17 +#define BASS_CONFIG_DEV_NOTIFY 33 #define BASS_CONFIG_IOS_NOTIFY 46 +#define BASS_CONFIG_ANDROID_JAVAVM 63 #define BASS_CONFIG_LIBSSL 64 +#define BASS_CONFIG_FILENAME 75 +#define BASS_CONFIG_FILEOPENPROCS 77 #define BASS_CONFIG_THREAD 0x40000000 // flag: thread-specific setting @@ -175,14 +187,18 @@ typedef DWORD HPLUGIN; // plugin handle #define BASS_IOS_SESSION_DUCK 2 #define BASS_IOS_SESSION_AMBIENT 4 #define BASS_IOS_SESSION_SPEAKER 8 -#define BASS_IOS_SESSION_DISABLE 16 +#define BASS_IOS_SESSION_DISABLE 0x10 +#define BASS_IOS_SESSION_DEACTIVATE 0x20 +#define BASS_IOS_SESSION_AIRPLAY 0x40 +#define BASS_IOS_SESSION_BTHFP 0x80 +#define BASS_IOS_SESSION_BTA2DP 0x100 // BASS_Init flags #define BASS_DEVICE_8BITS 1 // unused #define BASS_DEVICE_MONO 2 // mono #define BASS_DEVICE_3D 4 // unused #define BASS_DEVICE_16BITS 8 // limit output to 16-bit -#define BASS_DEVICE_REINIT 128 // reinitialize +#define BASS_DEVICE_REINIT 0x80 // reinitialize #define BASS_DEVICE_LATENCY 0x100 // unused #define BASS_DEVICE_CPSPEAKERS 0x400 // unused #define BASS_DEVICE_SPEAKERS 0x800 // force enabling of speaker assignment @@ -194,6 +210,8 @@ typedef DWORD HPLUGIN; // plugin handle #define BASS_DEVICE_AUDIOTRACK 0x20000 // use AudioTrack output #define BASS_DEVICE_DSOUND 0x40000 // use DirectSound output #define BASS_DEVICE_SOFTWARE 0x80000 // disable hardware/fastpath output +#define BASS_DEVICE_OPENSLES 0x100000 // use OpenSLES output +#define BASS_DEVICE_APPLEVOICE 0x200000 // use Apple voice processing // DirectSound interfaces (for use with BASS_GetDSoundObject) #define BASS_OBJECT_DS 1 // IDirectSound @@ -216,7 +234,7 @@ typedef struct { #define BASS_DEVICE_DEFAULT 2 #define BASS_DEVICE_INIT 4 #define BASS_DEVICE_LOOPBACK 8 -#define BASS_DEVICE_DEFAULTCOM 128 +#define BASS_DEVICE_DEFAULTCOM 0x80 #define BASS_DEVICE_TYPE_MASK 0xff000000 #define BASS_DEVICE_TYPE_NETWORK 0x01000000 @@ -234,15 +252,10 @@ typedef struct { // BASS_GetDeviceInfo flags #define BASS_DEVICES_AIRPLAY 0x1000000 +// Output device info structure typedef struct { - DWORD flags; // device capabilities (DSCAPS_xxx flags) - DWORD hwsize; // unused - DWORD hwfree; // unused - DWORD freesam; // unused - DWORD free3d; // unused - DWORD minrate; // unused - DWORD maxrate; // unused - BOOL eax; // unused + DWORD flags; // DirectSound capabilities (DSCAPS_xxx flags) + DWORD reserved[7]; DWORD minbuf; // recommended minimum buffer length in ms DWORD dsver; // DirectSound version DWORD latency; // average delay (in ms) before start of playback @@ -259,32 +272,22 @@ typedef struct { // Recording device info structure typedef struct { - DWORD flags; // device capabilities (DSCCAPS_xxx flags) - DWORD formats; // supported standard formats (WAVE_FORMAT_xxx flags) + DWORD flags; // DirectSound capabilities (DSCCAPS_xxx flags) + DWORD formats; // number of channels (in high 8 bits) DWORD inputs; // number of inputs BOOL singlein; // TRUE = only 1 input can be set at a time - DWORD freq; // current input rate + DWORD freq; // current sample rate } BASS_RECORDINFO; // BASS_RECORDINFO flags (from DSOUND.H) #define DSCCAPS_EMULDRIVER DSCAPS_EMULDRIVER // device does not have hardware DirectSound recording support #define DSCCAPS_CERTIFIED DSCAPS_CERTIFIED // device driver has been certified by Microsoft -// defines for formats field of BASS_RECORDINFO (from MMSYSTEM.H) -#ifndef WAVE_FORMAT_1M08 -#define WAVE_FORMAT_1M08 0x00000001 /* 11.025 kHz, Mono, 8-bit */ -#define WAVE_FORMAT_1S08 0x00000002 /* 11.025 kHz, Stereo, 8-bit */ -#define WAVE_FORMAT_1M16 0x00000004 /* 11.025 kHz, Mono, 16-bit */ -#define WAVE_FORMAT_1S16 0x00000008 /* 11.025 kHz, Stereo, 16-bit */ -#define WAVE_FORMAT_2M08 0x00000010 /* 22.05 kHz, Mono, 8-bit */ -#define WAVE_FORMAT_2S08 0x00000020 /* 22.05 kHz, Stereo, 8-bit */ -#define WAVE_FORMAT_2M16 0x00000040 /* 22.05 kHz, Mono, 16-bit */ -#define WAVE_FORMAT_2S16 0x00000080 /* 22.05 kHz, Stereo, 16-bit */ -#define WAVE_FORMAT_4M08 0x00000100 /* 44.1 kHz, Mono, 8-bit */ -#define WAVE_FORMAT_4S08 0x00000200 /* 44.1 kHz, Stereo, 8-bit */ -#define WAVE_FORMAT_4M16 0x00000400 /* 44.1 kHz, Mono, 16-bit */ -#define WAVE_FORMAT_4S16 0x00000800 /* 44.1 kHz, Stereo, 16-bit */ -#endif +// filetypes +#define BASS_FILE_NAME 0 // filename +#define BASS_FILE_MEM 1 // memory +#define BASS_FILE_MEMCOPY 3 // memory to copy +#define BASS_FILE_HANDLE 4 // handle/descriptor // Sample info structure typedef struct { @@ -303,19 +306,18 @@ typedef struct { DWORD iangle; // angle of inside projection cone DWORD oangle; // angle of outside projection cone float outvol; // delta-volume outside the projection cone - DWORD vam; // unused - DWORD priority; // unused + DWORD reserved[2]; } BASS_SAMPLE; #define BASS_SAMPLE_8BITS 1 // 8 bit -#define BASS_SAMPLE_FLOAT 256 // 32 bit floating-point #define BASS_SAMPLE_MONO 2 // mono #define BASS_SAMPLE_LOOP 4 // looped #define BASS_SAMPLE_3D 8 // 3D functionality -#define BASS_SAMPLE_SOFTWARE 16 // unused -#define BASS_SAMPLE_MUTEMAX 32 // mute at max distance (3D only) -#define BASS_SAMPLE_VAM 64 // unused -#define BASS_SAMPLE_FX 128 // unused +#define BASS_SAMPLE_SOFTWARE 0x10 // unused +#define BASS_SAMPLE_MUTEMAX 0x20 // mute at max distance (3D only) +#define BASS_SAMPLE_NOREORDER 0x40 // don't reorder channels to match speakers +#define BASS_SAMPLE_FX 0x80 // unused +#define BASS_SAMPLE_FLOAT 0x100 // 32 bit floating-point #define BASS_SAMPLE_OVER_VOL 0x10000 // override lowest volume #define BASS_SAMPLE_OVER_POS 0x20000 // override longest playing #define BASS_SAMPLE_OVER_DIST 0x30000 // override furthest from listener (3D only) @@ -354,35 +356,30 @@ typedef struct { #define BASS_MUSIC_NOSAMPLE 0x100000 // don't load the samples // Speaker assignment flags -#define BASS_SPEAKER_FRONT 0x1000000 // front speakers -#define BASS_SPEAKER_REAR 0x2000000 // rear/side speakers -#define BASS_SPEAKER_CENLFE 0x3000000 // center & LFE speakers (5.1) -#define BASS_SPEAKER_REAR2 0x4000000 // rear center speakers (7.1) -#define BASS_SPEAKER_N(n) ((n)<<24) // n'th pair of speakers (max 15) -#define BASS_SPEAKER_LEFT 0x10000000 // modifier: left -#define BASS_SPEAKER_RIGHT 0x20000000 // modifier: right -#define BASS_SPEAKER_FRONTLEFT BASS_SPEAKER_FRONT|BASS_SPEAKER_LEFT -#define BASS_SPEAKER_FRONTRIGHT BASS_SPEAKER_FRONT|BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_REARLEFT BASS_SPEAKER_REAR|BASS_SPEAKER_LEFT -#define BASS_SPEAKER_REARRIGHT BASS_SPEAKER_REAR|BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_CENTER BASS_SPEAKER_CENLFE|BASS_SPEAKER_LEFT -#define BASS_SPEAKER_LFE BASS_SPEAKER_CENLFE|BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_REAR2LEFT BASS_SPEAKER_REAR2|BASS_SPEAKER_LEFT -#define BASS_SPEAKER_REAR2RIGHT BASS_SPEAKER_REAR2|BASS_SPEAKER_RIGHT +#define BASS_SPEAKER_FRONT 0x1000000 // front speakers +#define BASS_SPEAKER_REAR 0x2000000 // rear speakers +#define BASS_SPEAKER_CENLFE 0x3000000 // center & LFE speakers (5.1) +#define BASS_SPEAKER_SIDE 0x4000000 // side speakers (7.1) +#define BASS_SPEAKER_N(n) ((n)<<24) // n'th pair of speakers (max 15) +#define BASS_SPEAKER_LEFT 0x10000000 // modifier: left +#define BASS_SPEAKER_RIGHT 0x20000000 // modifier: right +#define BASS_SPEAKER_FRONTLEFT BASS_SPEAKER_FRONT | BASS_SPEAKER_LEFT +#define BASS_SPEAKER_FRONTRIGHT BASS_SPEAKER_FRONT | BASS_SPEAKER_RIGHT +#define BASS_SPEAKER_REARLEFT BASS_SPEAKER_REAR | BASS_SPEAKER_LEFT +#define BASS_SPEAKER_REARRIGHT BASS_SPEAKER_REAR | BASS_SPEAKER_RIGHT +#define BASS_SPEAKER_CENTER BASS_SPEAKER_CENLFE | BASS_SPEAKER_LEFT +#define BASS_SPEAKER_LFE BASS_SPEAKER_CENLFE | BASS_SPEAKER_RIGHT +#define BASS_SPEAKER_SIDELEFT BASS_SPEAKER_SIDE | BASS_SPEAKER_LEFT +#define BASS_SPEAKER_SIDERIGHT BASS_SPEAKER_SIDE | BASS_SPEAKER_RIGHT +#define BASS_SPEAKER_REAR2 BASS_SPEAKER_SIDE +#define BASS_SPEAKER_REAR2LEFT BASS_SPEAKER_SIDELEFT +#define BASS_SPEAKER_REAR2RIGHT BASS_SPEAKER_SIDERIGHT #define BASS_ASYNCFILE 0x40000000 // read file asynchronously #define BASS_UNICODE 0x80000000 // UTF-16 +#define BASS_RECORD_OPENSLES 0x1000 // use OpenSLES #define BASS_RECORD_PAUSE 0x8000 // start recording paused -#define BASS_RECORD_ECHOCANCEL 0x2000 -#define BASS_RECORD_AGC 0x4000 - -// DX7 voice allocation & management flags -#define BASS_VAM_HARDWARE 1 -#define BASS_VAM_SOFTWARE 2 -#define BASS_VAM_TERM_TIME 4 -#define BASS_VAM_TERM_DIST 8 -#define BASS_VAM_TERM_PRIO 16 // Channel info structure typedef struct { @@ -424,6 +421,9 @@ typedef struct { #define BASS_CTYPE_MUSIC_IT 0x20004 #define BASS_CTYPE_MUSIC_MO3 0x00100 // MO3 flag +// BASS_PluginLoad flags +#define BASS_PLUGIN_PROC 1 + typedef struct { DWORD ctype; // channel type #if defined(_WIN32_WCE) || (defined(WINAPI_FAMILY) && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) @@ -444,8 +444,8 @@ typedef struct { // 3D vector (for 3D positions/velocities/orientations) typedef struct BASS_3DVECTOR { #ifdef __cplusplus - BASS_3DVECTOR() {}; - BASS_3DVECTOR(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}; + BASS_3DVECTOR() {} + BASS_3DVECTOR(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {} #endif float x; // +=right, -=left float y; // +=up, -=down @@ -460,8 +460,6 @@ typedef struct BASS_3DVECTOR { // software 3D mixing algorithms (used with BASS_CONFIG_3DALGORITHM) #define BASS_3DALG_DEFAULT 0 #define BASS_3DALG_OFF 1 -#define BASS_3DALG_FULL 2 -#define BASS_3DALG_LIGHT 3 // BASS_SampleGetChannel flags #define BASS_SAMCHAN_NEW 1 // get a new playback channel @@ -473,9 +471,10 @@ handle : The stream that needs writing buffer : Buffer to write the samples in length : Number of bytes to write user : The 'user' parameter value given when calling BASS_StreamCreate -RETURN : Number of bytes written. Set the BASS_STREAMPROC_END flag to end the stream. */ +RETURN : Number of bytes written and BASS_STREAMPROC_xxx flags */ -#define BASS_STREAMPROC_END 0x80000000 // end of user stream flag +#define BASS_STREAMPROC_AGAIN 0x40000000 // call again for remainder +#define BASS_STREAMPROC_END 0x80000000 // end the stream // Special STREAMPROCs #define STREAMPROC_DUMMY (STREAMPROC*)0 // "dummy" stream @@ -488,11 +487,12 @@ RETURN : Number of bytes written. Set the BASS_STREAMPROC_END flag to end the st #define STREAMFILE_BUFFER 1 #define STREAMFILE_BUFFERPUSH 2 -// User file stream callback functions +// User file callback functions typedef void (CALLBACK FILECLOSEPROC)(void *user); typedef QWORD (CALLBACK FILELENPROC)(void *user); typedef DWORD (CALLBACK FILEREADPROC)(void *buffer, DWORD length, void *user); typedef BOOL (CALLBACK FILESEEKPROC)(QWORD offset, void *user); +typedef void *(CALLBACK FILEOPENPROC)(const char *filename, DWORD flags); typedef struct { FILECLOSEPROC *close; @@ -501,6 +501,14 @@ typedef struct { FILESEEKPROC *seek; } BASS_FILEPROCS; +typedef struct { + FILECLOSEPROC *close; + FILELENPROC *length; + FILEREADPROC *read; + FILESEEKPROC *seek; + FILEOPENPROC *open; +} BASS_FILEOPENPROCS; + // BASS_StreamPutFileData options #define BASS_FILEDATA_END 0 // end & close the file @@ -517,6 +525,7 @@ typedef struct { #define BASS_FILEPOS_SIZE 8 #define BASS_FILEPOS_BUFFERING 9 #define BASS_FILEPOS_AVAILABLE 10 +#define BASS_FILEPOS_ASYNCSIZE 12 typedef void (CALLBACK DOWNLOADPROC)(const void *buffer, DWORD length, void *user); /* Internet stream download callback function. @@ -537,8 +546,10 @@ user : The 'user' parameter value given when calling BASS_StreamCreateURL */ #define BASS_SYNC_MUSICINST 1 #define BASS_SYNC_MUSICFX 3 #define BASS_SYNC_OGG_CHANGE 12 +#define BASS_SYNC_ATTRIB 13 #define BASS_SYNC_DEV_FAIL 14 #define BASS_SYNC_DEV_FORMAT 15 +#define BASS_SYNC_POS_RAW 16 #define BASS_SYNC_THREAD 0x20000000 // flag: call sync in other thread #define BASS_SYNC_MIXTIME 0x40000000 // flag: sync at mixtime, else at playtime #define BASS_SYNC_ONETIME 0x80000000 // flag: sync only once, else continuously @@ -566,6 +577,10 @@ length : Number of bytes user : The 'user' parameter value given when calling BASS_RecordStart RETURN : TRUE = continue recording, FALSE = stop */ +// Special RECORDPROCs +#define RECORDPROC_NONE (RECORDPROC*)0 // no RECORDPROC +#define RECORDPROC_TRUE (RECORDPROC*)-1 // only "return true" + // BASS_ChannelIsActive return values #define BASS_ACTIVE_STOPPED 0 #define BASS_ACTIVE_PLAYING 1 @@ -591,6 +606,10 @@ RETURN : TRUE = continue recording, FALSE = stop */ #define BASS_ATTRIB_USER 15 #define BASS_ATTRIB_TAIL 16 #define BASS_ATTRIB_PUSH_LIMIT 17 +#define BASS_ATTRIB_DOWNLOADPROC 18 +#define BASS_ATTRIB_VOLDSP 19 +#define BASS_ATTRIB_VOLDSP_PRIORITY 20 +#define BASS_ATTRIB_DOWNMIX 21 #define BASS_ATTRIB_MUSIC_AMPLIFY 0x100 #define BASS_ATTRIB_MUSIC_PANSEP 0x101 #define BASS_ATTRIB_MUSIC_PSCALER 0x102 @@ -601,13 +620,17 @@ RETURN : TRUE = continue recording, FALSE = stop */ #define BASS_ATTRIB_MUSIC_VOL_CHAN 0x200 // + channel # #define BASS_ATTRIB_MUSIC_VOL_INST 0x300 // + instrument # +// Channel attribute types +#define BASS_ATTRIBTYPE_FLOAT -1 +#define BASS_ATTRIBTYPE_INT -2 + // BASS_ChannelSlideAttribute flags #define BASS_SLIDE_LOG 0x1000000 // BASS_ChannelGetData flags #define BASS_DATA_AVAILABLE 0 // query how much data is buffered #define BASS_DATA_NOREMOVE 0x10000000 // flag: don't remove data from recording buffer -#define BASS_DATA_FIXED 0x20000000 // flag: return 8.24 fixed-point data +#define BASS_DATA_FIXED 0x20000000 // unused #define BASS_DATA_FLOAT 0x40000000 // flag: return floating-point sample data #define BASS_DATA_FFT256 0x80000000 // 256 sample FFT #define BASS_DATA_FFT512 0x80000001 // 512 FFT @@ -628,7 +651,7 @@ RETURN : TRUE = continue recording, FALSE = stop */ #define BASS_LEVEL_STEREO 2 // get stereo level #define BASS_LEVEL_RMS 4 // get RMS levels #define BASS_LEVEL_VOLPAN 8 // apply VOL/PAN attributes to the levels -#define BASS_LEVEL_NOREMOVE 16 // don't remove data from recording buffer +#define BASS_LEVEL_NOREMOVE 0x10 // don't remove data from recording buffer // BASS_ChannelGetTags types : what's returned #define BASS_TAG_ID3 0 // ID3v1 tags : TAG_ID3 structure @@ -649,6 +672,8 @@ RETURN : TRUE = continue recording, FALSE = stop */ #define BASS_TAG_ID3V2_2 17 // ID3v2 tags (2nd block) : variable length block #define BASS_TAG_AM_MIME 18 // Android Media MIME type : ASCII string #define BASS_TAG_LOCATION 19 // redirected URL : ASCII string +#define BASS_TAG_ID3V2_BINARY 20 // ID3v2 tags : TAB_BINARY +#define BASS_TAG_ID3V2_2_BINARY 21 // ID3v2 tags (2nd block) : TAB_BINARY #define BASS_TAG_RIFF_INFO 0x100 // RIFF "INFO" tags : series of null-terminated ANSI strings #define BASS_TAG_RIFF_BEXT 0x101 // RIFF/BWF "bext" tags : TAG_BEXT structure #define BASS_TAG_RIFF_CART 0x102 // RIFF/BWF "cart" tags : TAG_CART structure @@ -656,6 +681,7 @@ RETURN : TRUE = continue recording, FALSE = stop */ #define BASS_TAG_RIFF_CUE 0x104 // RIFF "cue " chunk : TAG_CUE structure #define BASS_TAG_RIFF_SMPL 0x105 // RIFF "smpl" chunk : TAG_SMPL structure #define BASS_TAG_APE_BINARY 0x1000 // + index #, binary APE tag : TAG_APE_BINARY structure +#define BASS_TAG_MP4_COVERART 0x1400 // + index #, MP4 cover art : TAG_BINARY structure #define BASS_TAG_MUSIC_NAME 0x10000 // MOD music name : ANSI string #define BASS_TAG_MUSIC_MESSAGE 0x10001 // MOD message : ANSI string #define BASS_TAG_MUSIC_ORDERS 0x10002 // MOD order list : BYTE array of pattern numbers @@ -663,6 +689,7 @@ RETURN : TRUE = continue recording, FALSE = stop */ #define BASS_TAG_MUSIC_INST 0x10100 // + instrument #, MOD instrument name : ANSI string #define BASS_TAG_MUSIC_CHAN 0x10200 // + channel #, MOD channel name : ANSI string #define BASS_TAG_MUSIC_SAMPLE 0x10300 // + sample #, MOD sample name : ANSI string +#define BASS_TAG_INCREF 0x20000000 // flag: increment channel's reference count // ID3v1 tag structure typedef struct { @@ -675,6 +702,12 @@ typedef struct { BYTE genre; } TAG_ID3; +// Binary tag structure +typedef struct { + const void *data; + DWORD length; +} TAG_BINARY; + // Binary APE tag structure typedef struct { const char *key; @@ -830,8 +863,11 @@ typedef const WAVEFORMATEX *LPCWAVEFORMATEX; #define BASS_POS_BYTE 0 // byte position #define BASS_POS_MUSIC_ORDER 1 // order.row position, MAKELONG(order,row) #define BASS_POS_OGG 3 // OGG bitstream number +#define BASS_POS_TRACK 4 // track number +#define BASS_POS_RAW 6 // monotonic byte position #define BASS_POS_END 0x10 // trimmed end position #define BASS_POS_LOOP 0x11 // loop start positiom +#define BASS_POS_DSP 0x800000 // flag: get the DSP position #define BASS_POS_FLUSH 0x1000000 // flag: flush decoder/FX buffers #define BASS_POS_RESET 0x2000000 // flag: reset user file buffers #define BASS_POS_RELATIVE 0x4000000 // flag: seek relative to the current position @@ -860,6 +896,14 @@ typedef const WAVEFORMATEX *LPCWAVEFORMATEX; #define BASS_INPUT_TYPE_AUX 0x09000000 #define BASS_INPUT_TYPE_ANALOG 0x0a000000 +// BASS_ChannelSetDSPEx flags +#define BASS_DSP_READONLY 1 +#define BASS_DSP_FLOAT 2 +#define BASS_DSP_FREECALL 4 +#define BASS_DSP_BYPASS 0x400000 +#define BASS_DSP_MONO_N(n) ((n)<<24) +#define BASS_DSP_STEREO_N(n) (BASS_DSP_MONO_N(n) | 0x800000) + // BASS_ChannelSetFX effect types #define BASS_FX_DX8_CHORUS 0 #define BASS_FX_DX8_COMPRESSOR 1 @@ -963,6 +1007,16 @@ typedef struct { DWORD lCurve; } BASS_FX_VOLUME_PARAM; +typedef void (CALLBACK DEVICENOTIFYPROC)(DWORD notify); +/* Device notification callback function. +notify : The notification (BASS_DEVICENOTIFY_xxx) */ + +#define BASS_DEVICENOTIFY_ENABLED 0 // a device has been added or removed +#define BASS_DEVICENOTIFY_DEFAULT 1 // the default output device has changed +#define BASS_DEVICENOTIFY_REC_DEFAULT 2 // the default recording device has changed +#define BASS_DEVICENOTIFY_DEFAULTCOM 3 // the default communication output device has changed +#define BASS_DEVICENOTIFY_REC_DEFAULTCOM 4 // the default communication recording device has changed + typedef void (CALLBACK IOSNOTIFYPROC)(DWORD status); /* iOS notification callback function. status : The notification (BASS_IOSNOTIFY_xxx) */ @@ -1010,7 +1064,7 @@ BOOL BASSDEF(BASS_PluginFree)(HPLUGIN handle); BOOL BASSDEF(BASS_PluginEnable)(HPLUGIN handle, BOOL enable); const BASS_PLUGININFO *BASSDEF(BASS_PluginGetInfo)(HPLUGIN handle); -HSAMPLE BASSDEF(BASS_SampleLoad)(BOOL mem, const void *file, QWORD offset, DWORD length, DWORD max, DWORD flags); +HSAMPLE BASSDEF(BASS_SampleLoad)(DWORD filetype, const void *file, QWORD offset, DWORD length, DWORD max, DWORD flags); HSAMPLE BASSDEF(BASS_SampleCreate)(DWORD length, DWORD freq, DWORD chans, DWORD max, DWORD flags); BOOL BASSDEF(BASS_SampleFree)(HSAMPLE handle); BOOL BASSDEF(BASS_SampleSetData)(HSAMPLE handle, const void *buffer); @@ -1022,15 +1076,16 @@ DWORD BASSDEF(BASS_SampleGetChannels)(HSAMPLE handle, HCHANNEL *channels); BOOL BASSDEF(BASS_SampleStop)(HSAMPLE handle); HSTREAM BASSDEF(BASS_StreamCreate)(DWORD freq, DWORD chans, DWORD flags, STREAMPROC *proc, void *user); -HSTREAM BASSDEF(BASS_StreamCreateFile)(BOOL mem, const void *file, QWORD offset, QWORD length, DWORD flags); +HSTREAM BASSDEF(BASS_StreamCreateFile)(DWORD filetype, const void *file, QWORD offset, QWORD length, DWORD flags); HSTREAM BASSDEF(BASS_StreamCreateURL)(const char *url, DWORD offset, DWORD flags, DOWNLOADPROC *proc, void *user); HSTREAM BASSDEF(BASS_StreamCreateFileUser)(DWORD system, DWORD flags, const BASS_FILEPROCS *proc, void *user); +BOOL BASSDEF(BASS_StreamCancel)(void *user); BOOL BASSDEF(BASS_StreamFree)(HSTREAM handle); QWORD BASSDEF(BASS_StreamGetFilePosition)(HSTREAM handle, DWORD mode); DWORD BASSDEF(BASS_StreamPutData)(HSTREAM handle, const void *buffer, DWORD length); DWORD BASSDEF(BASS_StreamPutFileData)(HSTREAM handle, const void *buffer, DWORD length); -HMUSIC BASSDEF(BASS_MusicLoad)(BOOL mem, const void *file, QWORD offset, DWORD length, DWORD flags, DWORD freq); +HMUSIC BASSDEF(BASS_MusicLoad)(DWORD filetype, const void *file, QWORD offset, DWORD length, DWORD flags, DWORD freq); BOOL BASSDEF(BASS_MusicFree)(HMUSIC handle); BOOL BASSDEF(BASS_RecordGetDeviceInfo)(DWORD device, BASS_DEVICEINFO *info); @@ -1053,8 +1108,10 @@ BOOL BASSDEF(BASS_ChannelGetInfo)(DWORD handle, BASS_CHANNELINFO *info); const char *BASSDEF(BASS_ChannelGetTags)(DWORD handle, DWORD tags); DWORD BASSDEF(BASS_ChannelFlags)(DWORD handle, DWORD flags, DWORD mask); BOOL BASSDEF(BASS_ChannelLock)(DWORD handle, BOOL lock); +BOOL BASSDEF(BASS_ChannelRef)(DWORD handle, BOOL inc); BOOL BASSDEF(BASS_ChannelFree)(DWORD handle); BOOL BASSDEF(BASS_ChannelPlay)(DWORD handle, BOOL restart); +BOOL BASSDEF(BASS_ChannelStart)(DWORD handle); BOOL BASSDEF(BASS_ChannelStop)(DWORD handle); BOOL BASSDEF(BASS_ChannelPause)(DWORD handle); BOOL BASSDEF(BASS_ChannelUpdate)(DWORD handle, DWORD length); @@ -1062,8 +1119,8 @@ BOOL BASSDEF(BASS_ChannelSetAttribute)(DWORD handle, DWORD attrib, float value); BOOL BASSDEF(BASS_ChannelGetAttribute)(DWORD handle, DWORD attrib, float *value); BOOL BASSDEF(BASS_ChannelSlideAttribute)(DWORD handle, DWORD attrib, float value, DWORD time); BOOL BASSDEF(BASS_ChannelIsSliding)(DWORD handle, DWORD attrib); -BOOL BASSDEF(BASS_ChannelSetAttributeEx)(DWORD handle, DWORD attrib, void *value, DWORD size); -DWORD BASSDEF(BASS_ChannelGetAttributeEx)(DWORD handle, DWORD attrib, void *value, DWORD size); +BOOL BASSDEF(BASS_ChannelSetAttributeEx)(DWORD handle, DWORD attrib, void *value, DWORD typesize); +DWORD BASSDEF(BASS_ChannelGetAttributeEx)(DWORD handle, DWORD attrib, void *value, DWORD typesize); BOOL BASSDEF(BASS_ChannelSet3DAttributes)(DWORD handle, int mode, float min, float max, int iangle, int oangle, float outvol); BOOL BASSDEF(BASS_ChannelGet3DAttributes)(DWORD handle, DWORD *mode, float *min, float *max, DWORD *iangle, DWORD *oangle, float *outvol); BOOL BASSDEF(BASS_ChannelSet3DPosition)(DWORD handle, const BASS_3DVECTOR *pos, const BASS_3DVECTOR *orient, const BASS_3DVECTOR *vel); @@ -1079,14 +1136,17 @@ BOOL BASSDEF(BASS_ChannelRemoveSync)(DWORD handle, HSYNC sync); BOOL BASSDEF(BASS_ChannelSetLink)(DWORD handle, DWORD chan); BOOL BASSDEF(BASS_ChannelRemoveLink)(DWORD handle, DWORD chan); HDSP BASSDEF(BASS_ChannelSetDSP)(DWORD handle, DSPPROC *proc, void *user, int priority); +HDSP BASSDEF(BASS_ChannelSetDSPEx)(DWORD handle, DSPPROC *proc, void *user, int priority, DWORD flags); BOOL BASSDEF(BASS_ChannelRemoveDSP)(DWORD handle, HDSP dsp); HFX BASSDEF(BASS_ChannelSetFX)(DWORD handle, DWORD type, int priority); BOOL BASSDEF(BASS_ChannelRemoveFX)(DWORD handle, HFX fx); BOOL BASSDEF(BASS_FXSetParameters)(HFX handle, const void *params); BOOL BASSDEF(BASS_FXGetParameters)(HFX handle, void *params); -BOOL BASSDEF(BASS_FXSetPriority)(HFX handle, int priority); +BOOL BASSDEF(BASS_FXSetPriority)(DWORD handle, int priority); +BOOL BASSDEF(BASS_FXSetBypass)(DWORD handle, BOOL bypass); BOOL BASSDEF(BASS_FXReset)(DWORD handle); +BOOL BASSDEF(BASS_FXFree)(DWORD handle); #ifdef __cplusplus } @@ -1097,19 +1157,19 @@ static inline HPLUGIN BASS_PluginLoad(const WCHAR *file, DWORD flags) return BASS_PluginLoad((const char*)file, flags | BASS_UNICODE); } -static inline HMUSIC BASS_MusicLoad(BOOL mem, const WCHAR *file, QWORD offset, DWORD length, DWORD flags, DWORD freq) +static inline HMUSIC BASS_MusicLoad(DWORD filetype, const WCHAR *file, QWORD offset, DWORD length, DWORD flags, DWORD freq) { - return BASS_MusicLoad(mem, (const void*)file, offset, length, flags | BASS_UNICODE, freq); + return BASS_MusicLoad(filetype, (const void*)file, offset, length, flags | BASS_UNICODE, freq); } -static inline HSAMPLE BASS_SampleLoad(BOOL mem, const WCHAR *file, QWORD offset, DWORD length, DWORD max, DWORD flags) +static inline HSAMPLE BASS_SampleLoad(DWORD filetype, const WCHAR *file, QWORD offset, DWORD length, DWORD max, DWORD flags) { - return BASS_SampleLoad(mem, (const void*)file, offset, length, max, flags | BASS_UNICODE); + return BASS_SampleLoad(filetype, (const void*)file, offset, length, max, flags | BASS_UNICODE); } -static inline HSTREAM BASS_StreamCreateFile(BOOL mem, const WCHAR *file, QWORD offset, QWORD length, DWORD flags) +static inline HSTREAM BASS_StreamCreateFile(DWORD filetype, const WCHAR *file, QWORD offset, QWORD length, DWORD flags) { - return BASS_StreamCreateFile(mem, (const void*)file, offset, length, flags | BASS_UNICODE); + return BASS_StreamCreateFile(filetype, (const void*)file, offset, length, flags | BASS_UNICODE); } static inline HSTREAM BASS_StreamCreateURL(const WCHAR *url, DWORD offset, DWORD flags, DOWNLOADPROC *proc, void *user) diff --git a/shared/bass/bass.lib b/shared/bass/bass.lib index 085afce5a..ee14a4290 100644 Binary files a/shared/bass/bass.lib and b/shared/bass/bass.lib differ diff --git a/stb/stb_image.h b/stb/stb_image.h index 5e807a0a6..9eedabedc 100644 --- a/stb/stb_image.h +++ b/stb/stb_image.h @@ -1,4 +1,4 @@ -/* stb_image - v2.28 - public domain image loader - http://nothings.org/stb +/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb no warranty implied; use at your own risk Do this: @@ -48,6 +48,8 @@ LICENSE RECENT REVISION HISTORY: + 2.30 (2024-05-31) avoid erroneous gcc warning + 2.29 (2023-05-xx) optimizations 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes 2.26 (2020-07-13) many minor fixes @@ -1072,8 +1074,8 @@ static int stbi__addints_valid(int a, int b) return a <= INT_MAX - b; } -// returns 1 if the product of two signed shorts is valid, 0 on overflow. -static int stbi__mul2shorts_valid(short a, short b) +// returns 1 if the product of two ints fits in a signed short, 0 on overflow. +static int stbi__mul2shorts_valid(int a, int b) { if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid @@ -3384,13 +3386,13 @@ static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) return 1; } -static int stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) { // some JPEGs have junk at end, skip over it but if we find what looks // like a valid marker, resume there while (!stbi__at_eof(j->s)) { - int x = stbi__get8(j->s); - while (x == 255) { // might be a marker + stbi_uc x = stbi__get8(j->s); + while (x == 0xff) { // might be a marker if (stbi__at_eof(j->s)) return STBI__MARKER_none; x = stbi__get8(j->s); if (x != 0x00 && x != 0xff) { @@ -4176,6 +4178,7 @@ typedef struct { stbi_uc *zbuffer, *zbuffer_end; int num_bits; + int hit_zeof_once; stbi__uint32 code_buffer; char *zout; @@ -4242,9 +4245,20 @@ stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) int b,s; if (a->num_bits < 16) { if (stbi__zeof(a)) { - return -1; /* report error for unexpected end of data. */ + if (!a->hit_zeof_once) { + // This is the first time we hit eof, insert 16 extra padding btis + // to allow us to keep going; if we actually consume any of them + // though, that is invalid data. This is caught later. + a->hit_zeof_once = 1; + a->num_bits += 16; // add 16 implicit zero bits + } else { + // We already inserted our extra 16 padding bits and are again + // out, this stream is actually prematurely terminated. + return -1; + } + } else { + stbi__fill_bits(a); } - stbi__fill_bits(a); } b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; if (b) { @@ -4309,6 +4323,13 @@ static int stbi__parse_huffman_block(stbi__zbuf *a) int len,dist; if (z == 256) { a->zout = zout; + if (a->hit_zeof_once && a->num_bits < 16) { + // The first time we hit zeof, we inserted 16 extra zero bits into our bit + // buffer so the decoder can just do its speculative decoding. But if we + // actually consumed any of those bits (which is the case when num_bits < 16), + // the stream actually read past the end so it is malformed. + return stbi__err("unexpected end","Corrupt PNG"); + } return 1; } if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data @@ -4320,7 +4341,7 @@ static int stbi__parse_huffman_block(stbi__zbuf *a) dist = stbi__zdist_base[z]; if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); - if (zout + len > a->zout_end) { + if (len > a->zout_end - zout) { if (!stbi__zexpand(a, zout, len)) return 0; zout = a->zout; } @@ -4464,6 +4485,7 @@ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) if (!stbi__parse_zlib_header(a)) return 0; a->num_bits = 0; a->code_buffer = 0; + a->hit_zeof_once = 0; do { final = stbi__zreceive(a,1); type = stbi__zreceive(a,2); @@ -4619,9 +4641,8 @@ enum { STBI__F_up=2, STBI__F_avg=3, STBI__F_paeth=4, - // synthetic filters used for first scanline to avoid needing a dummy row of 0s - STBI__F_avg_first, - STBI__F_paeth_first + // synthetic filter used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first }; static stbi_uc first_row_filter[5] = @@ -4630,29 +4651,56 @@ static stbi_uc first_row_filter[5] = STBI__F_sub, STBI__F_none, STBI__F_avg_first, - STBI__F_paeth_first + STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub }; static int stbi__paeth(int a, int b, int c) { - int p = a + b - c; - int pa = abs(p-a); - int pb = abs(p-b); - int pc = abs(p-c); - if (pa <= pb && pa <= pc) return a; - if (pb <= pc) return b; - return c; + // This formulation looks very different from the reference in the PNG spec, but is + // actually equivalent and has favorable data dependencies and admits straightforward + // generation of branch-free code, which helps performance significantly. + int thresh = c*3 - (a + b); + int lo = a < b ? a : b; + int hi = a < b ? b : a; + int t0 = (hi <= thresh) ? lo : c; + int t1 = (thresh <= lo) ? hi : t0; + return t1; } static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; +// adds an extra all-255 alpha channel +// dest == src is legal +// img_n must be 1 or 3 +static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n) +{ + int i; + // must process data backwards since we allow dest==src + if (img_n == 1) { + for (i=x-1; i >= 0; --i) { + dest[i*2+1] = 255; + dest[i*2+0] = src[i]; + } + } else { + STBI_ASSERT(img_n == 3); + for (i=x-1; i >= 0; --i) { + dest[i*4+3] = 255; + dest[i*4+2] = src[i*3+2]; + dest[i*4+1] = src[i*3+1]; + dest[i*4+0] = src[i*3+0]; + } + } +} + // create the png data from post-deflated data static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) { - int bytes = (depth == 16? 2 : 1); + int bytes = (depth == 16 ? 2 : 1); stbi__context *s = a->s; stbi__uint32 i,j,stride = x*out_n*bytes; stbi__uint32 img_len, img_width_bytes; + stbi_uc *filter_buf; + int all_ok = 1; int k; int img_n = s->img_n; // copy it into a local for later @@ -4664,8 +4712,11 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into if (!a->out) return stbi__err("outofmem", "Out of memory"); + // note: error exits here don't need to clean up a->out individually, + // stbi__do_png always does on error. if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); img_width_bytes = (((img_n * x * depth) + 7) >> 3); + if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG"); img_len = (img_width_bytes + 1) * y; // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, @@ -4673,189 +4724,137 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r // so just check for raw_len < img_len always. if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + // Allocate two scan lines worth of filter workspace buffer. + filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0); + if (!filter_buf) return stbi__err("outofmem", "Out of memory"); + + // Filtering for low-bit-depth images + if (depth < 8) { + filter_bytes = 1; + width = img_width_bytes; + } + for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *prior; + // cur/prior filter buffers alternate + stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes; + stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes; + stbi_uc *dest = a->out + stride*j; + int nk = width * filter_bytes; int filter = *raw++; - if (filter > 4) - return stbi__err("invalid filter","Corrupt PNG"); - - if (depth < 8) { - if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); - cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place - filter_bytes = 1; - width = img_width_bytes; + // check filter type + if (filter > 4) { + all_ok = stbi__err("invalid filter","Corrupt PNG"); + break; } - prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above // if first row, use special filter that doesn't sample previous row if (j == 0) filter = first_row_filter[filter]; - // handle first byte explicitly - for (k=0; k < filter_bytes; ++k) { - switch (filter) { - case STBI__F_none : cur[k] = raw[k]; break; - case STBI__F_sub : cur[k] = raw[k]; break; - case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; - case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; - case STBI__F_avg_first : cur[k] = raw[k]; break; - case STBI__F_paeth_first: cur[k] = raw[k]; break; - } - } - - if (depth == 8) { - if (img_n != out_n) - cur[img_n] = 255; // first pixel - raw += img_n; - cur += out_n; - prior += out_n; - } else if (depth == 16) { - if (img_n != out_n) { - cur[filter_bytes] = 255; // first pixel top byte - cur[filter_bytes+1] = 255; // first pixel bottom byte - } - raw += filter_bytes; - cur += output_bytes; - prior += output_bytes; - } else { - raw += 1; - cur += 1; - prior += 1; + // perform actual filtering + switch (filter) { + case STBI__F_none: + memcpy(cur, raw, nk); + break; + case STBI__F_sub: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); + break; + case STBI__F_up: + for (k = 0; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); + break; + case STBI__F_avg: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); + break; + case STBI__F_paeth: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0) + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes])); + break; + case STBI__F_avg_first: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); + break; } - // this is a little gross, so that we don't switch per-pixel or per-component - if (depth < 8 || img_n == out_n) { - int nk = (width - 1)*filter_bytes; - #define STBI__CASE(f) \ - case f: \ - for (k=0; k < nk; ++k) - switch (filter) { - // "none" filter turns into a memcpy here; make that explicit. - case STBI__F_none: memcpy(cur, raw, nk); break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; - } - #undef STBI__CASE - raw += nk; - } else { - STBI_ASSERT(img_n+1 == out_n); - #define STBI__CASE(f) \ - case f: \ - for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ - for (k=0; k < filter_bytes; ++k) - switch (filter) { - STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; - } - #undef STBI__CASE - - // the loop above sets the high byte of the pixels' alpha, but for - // 16 bit png files we also need the low byte set. we'll do that here. - if (depth == 16) { - cur = a->out + stride*j; // start at the beginning of the row again - for (i=0; i < x; ++i,cur+=output_bytes) { - cur[filter_bytes+1] = 255; - } - } - } - } + raw += nk; - // we make a separate pass to expand bits to pixels; for performance, - // this could run two scanlines behind the above code, so it won't - // intefere with filtering but will still be in the cache. - if (depth < 8) { - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; - // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit - // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + // expand decoded bits in cur to dest, also adding an extra alpha channel if desired + if (depth < 8) { stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + stbi_uc *in = cur; + stbi_uc *out = dest; + stbi_uc inb = 0; + stbi__uint32 nsmp = x*img_n; - // note that the final byte might overshoot and write more data than desired. - // we can allocate enough data that this never writes out of memory, but it - // could also overwrite the next scanline. can it overwrite non-empty data - // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. - // so we need to explicitly clamp the final ones - + // expand bits to bytes first if (depth == 4) { - for (k=x*img_n; k >= 2; k-=2, ++in) { - *cur++ = scale * ((*in >> 4) ); - *cur++ = scale * ((*in ) & 0x0f); + for (i=0; i < nsmp; ++i) { + if ((i & 1) == 0) inb = *in++; + *out++ = scale * (inb >> 4); + inb <<= 4; } - if (k > 0) *cur++ = scale * ((*in >> 4) ); } else if (depth == 2) { - for (k=x*img_n; k >= 4; k-=4, ++in) { - *cur++ = scale * ((*in >> 6) ); - *cur++ = scale * ((*in >> 4) & 0x03); - *cur++ = scale * ((*in >> 2) & 0x03); - *cur++ = scale * ((*in ) & 0x03); + for (i=0; i < nsmp; ++i) { + if ((i & 3) == 0) inb = *in++; + *out++ = scale * (inb >> 6); + inb <<= 2; } - if (k > 0) *cur++ = scale * ((*in >> 6) ); - if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); - if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); - } else if (depth == 1) { - for (k=x*img_n; k >= 8; k-=8, ++in) { - *cur++ = scale * ((*in >> 7) ); - *cur++ = scale * ((*in >> 6) & 0x01); - *cur++ = scale * ((*in >> 5) & 0x01); - *cur++ = scale * ((*in >> 4) & 0x01); - *cur++ = scale * ((*in >> 3) & 0x01); - *cur++ = scale * ((*in >> 2) & 0x01); - *cur++ = scale * ((*in >> 1) & 0x01); - *cur++ = scale * ((*in ) & 0x01); + } else { + STBI_ASSERT(depth == 1); + for (i=0; i < nsmp; ++i) { + if ((i & 7) == 0) inb = *in++; + *out++ = scale * (inb >> 7); + inb <<= 1; } - if (k > 0) *cur++ = scale * ((*in >> 7) ); - if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); - if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); - if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); - if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); - if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); - if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); } - if (img_n != out_n) { - int q; - // insert alpha = 255 - cur = a->out + stride*j; + + // insert alpha=255 values if desired + if (img_n != out_n) + stbi__create_png_alpha_expand8(dest, dest, x, img_n); + } else if (depth == 8) { + if (img_n == out_n) + memcpy(dest, cur, x*img_n); + else + stbi__create_png_alpha_expand8(dest, cur, x, img_n); + } else if (depth == 16) { + // convert the image data from big-endian to platform-native + stbi__uint16 *dest16 = (stbi__uint16*)dest; + stbi__uint32 nsmp = x*img_n; + + if (img_n == out_n) { + for (i = 0; i < nsmp; ++i, ++dest16, cur += 2) + *dest16 = (cur[0] << 8) | cur[1]; + } else { + STBI_ASSERT(img_n+1 == out_n); if (img_n == 1) { - for (q=x-1; q >= 0; --q) { - cur[q*2+1] = 255; - cur[q*2+0] = cur[q]; + for (i = 0; i < x; ++i, dest16 += 2, cur += 2) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = 0xffff; } } else { STBI_ASSERT(img_n == 3); - for (q=x-1; q >= 0; --q) { - cur[q*4+3] = 255; - cur[q*4+2] = cur[q*3+2]; - cur[q*4+1] = cur[q*3+1]; - cur[q*4+0] = cur[q*3+0]; + for (i = 0; i < x; ++i, dest16 += 4, cur += 6) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = (cur[2] << 8) | cur[3]; + dest16[2] = (cur[4] << 8) | cur[5]; + dest16[3] = 0xffff; } } } } - } else if (depth == 16) { - // force the image data from big-endian to platform-native. - // this is done in a separate pass due to the decoding relying - // on the data being untouched, but could probably be done - // per-line during decode if care is taken. - stbi_uc *cur = a->out; - stbi__uint16 *cur16 = (stbi__uint16*)cur; - - for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { - *cur16 = (cur[0] << 8) | cur[1]; - } } + STBI_FREE(filter_buf); + if (!all_ok) return 0; + return 1; } @@ -5161,9 +5160,11 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } if (z->depth == 16) { - for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning + tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is } else { - for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + for (k = 0; k < s->img_n && k < 3; ++k) + tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger } } break; diff --git a/tools/premake/linux-clang-example/premake5 b/tools/premake/linux-clang-example/premake5 index b5354b58c..53efa21fb 100755 Binary files a/tools/premake/linux-clang-example/premake5 and b/tools/premake/linux-clang-example/premake5 differ diff --git a/tools/premake/premake5.exe b/tools/premake/premake5.exe index d9877c7a9..10e1b037e 100644 Binary files a/tools/premake/premake5.exe and b/tools/premake/premake5.exe differ