# Redirect memchr imported by the Ruby DLL to another implementation.
require 'fiddle'

module PatchMemchr
  PAGE_READWRITE = 0x04

  module_function

  def read(addr, len) = Fiddle::Pointer.new(addr)[0, len]

  def apply(new_addr, dll_name = 'x64-ucrt-ruby400.dll')
    k32 = Fiddle.dlopen('kernel32.dll')
    get_module = Fiddle::Function.new(k32['GetModuleHandleA'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOIDP)
    protect = Fiddle::Function.new(k32['VirtualProtect'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T, Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
    base = get_module.call(dll_name).to_i
    raise "#{dll_name} not loaded" if base.zero?
    nt = base + read(base + 0x3C, 4).unpack1('l<')
    opt = nt + 24
    raise 'not PE32+' unless read(opt, 2).unpack1('S<') == 0x20b
    import_rva = read(opt + 112 + 8, 4).unpack1('L<')
    desc = base + import_rva
    patched = []
    loop do
      oft, _ts, _fc, name_rva, ft = read(desc, 20).unpack('L<5')
      break if oft.zero? && name_rva.zero? && ft.zero?
      i = 0
      loop do
        thunk = read(base + oft + i * 8, 8).unpack1('Q<')
        break if thunk.zero?
        if (thunk >> 63).zero? && Fiddle::Pointer.new(base + thunk + 2).to_s == 'memchr'
          slot = base + ft + i * 8
          old = Fiddle::Pointer.malloc(4, Fiddle::RUBY_FREE)
          # VirtualProtect returns 0 on failure, and 0 is truthy in Ruby.
          raise 'VirtualProtect failed' if protect.call(slot, 8, PAGE_READWRITE, old).zero?
          Fiddle::Pointer.new(slot)[0, 8] = [new_addr].pack('Q<')
          raise 'VirtualProtect failed to restore' if protect.call(slot, 8, old[0, 4].unpack1('L<'), old).zero?
          raise 'import slot was not updated' unless read(slot, 8).unpack1('Q<') == new_addr
          patched << Fiddle::Pointer.new(base + name_rva).to_s
        end
        i += 1
      end
      desc += 20
    end
    patched
  end
end

variant = ENV['MEMCHR'] || 'ucrt'
unless variant == 'ucrt'
  addr = case variant
         when 'swar' then Fiddle.dlopen(File.join(__dir__, 'fastmemchr.dll'))['swar_memchr']
         when 'sse2' then Fiddle.dlopen(File.join(__dir__, 'fastmemchr.dll'))['sse2_memchr']
         else raise "unknown MEMCHR=#{variant}"
         end
  dlls = PatchMemchr.apply(addr)
  raise 'memchr import was not found, nothing was patched' if dlls.empty?
  warn "memchr -> #{variant} (patched import from #{dlls.join(', ')})"
end
s = ("a" * 1023 + "\n").b * 1000
raise 'index broken' unless s.index("\n") == 1023 && s.index("\n", 1024) == 2047 && s.each_line.count == 1000
