/*
 *  Copyright (C) 2010-2014 JPEXS
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.jpexs.process.win32;

import com.jpexs.helpers.ProgressListener;
import com.jpexs.process.ProcessTools;
import com.sun.jna.Memory;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Advapi32;
import com.sun.jna.platform.win32.BITMAP;
import com.sun.jna.platform.win32.BaseTSD;
import com.sun.jna.platform.win32.Gdi32;
import com.sun.jna.platform.win32.ICONINFO;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.MEMORY_BASIC_INFORMATION;
import com.sun.jna.platform.win32.PROCESSENTRY32;
import com.sun.jna.platform.win32.Psapi;
import com.sun.jna.platform.win32.SHFILEINFO;
import com.sun.jna.platform.win32.Shell32;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinBase;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.NativeLongByReference;
import com.sun.jna.ptr.PointerByReference;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author JPEXS
 */
public class Win32ProcessTools extends ProcessTools {

    private static long pointerToAddress(Pointer p) {
        String s = p.toString();
        s = s.replace("native@0x", "");
        return Long.parseLong(s, 16);
    }

    public static List<MEMORY_BASIC_INFORMATION> getPageRanges(WinNT.HANDLE hOtherProcess) {
        List<MEMORY_BASIC_INFORMATION> ret = new ArrayList<>();
        MEMORY_BASIC_INFORMATION mbi;
        WinBase.SYSTEM_INFO si = new WinBase.SYSTEM_INFO();
        Kernel32.INSTANCE.GetSystemInfo(si);
        Pointer lpMem = si.lpMinimumApplicationAddress;
        while (pointerToAddress(lpMem) < pointerToAddress(si.lpMaximumApplicationAddress)) {
            mbi = new MEMORY_BASIC_INFORMATION();
            BaseTSD.SIZE_T t = Kernel32.INSTANCE.VirtualQueryEx(hOtherProcess, lpMem, mbi, new BaseTSD.SIZE_T(mbi.size()));
            if (t.longValue() == 0) {
                Logger.getLogger(Win32ProcessTools.class.getName()).log(Level.SEVERE, "Cannot get page ranges. Last error:" + Kernel32.INSTANCE.GetLastError());
                break;
            }
            ret.add(mbi);
            lpMem = new Pointer(pointerToAddress(mbi.baseAddress) + mbi.regionSize.longValue());
        }
        return ret;
    }

    public static void printMemInfo(MEMORY_BASIC_INFORMATION mbi) {
        System.out.println("Region size:" + mbi.regionSize);
        String stateStr = "";
        if ((mbi.state.intValue() & Kernel32.MEM_COMMIT) == Kernel32.MEM_COMMIT) {
            stateStr += " commit";
        }
        if ((mbi.state.intValue() & Kernel32.MEM_FREE) == Kernel32.MEM_FREE) {
            stateStr += " free";
        }
        if ((mbi.state.intValue() & Kernel32.MEM_RESERVE) == Kernel32.MEM_RESERVE) {
            stateStr += " reserve";
        }
        stateStr = stateStr.trim();

        String typeStr = "";
        if ((mbi.type.intValue() & Kernel32.MEM_IMAGE) == Kernel32.MEM_IMAGE) {
            typeStr += " image";
        }
        if ((mbi.type.intValue() & Kernel32.MEM_MAPPED) == Kernel32.MEM_MAPPED) {
            typeStr += " mapped";
        }
        if ((mbi.type.intValue() & Kernel32.MEM_PRIVATE) == Kernel32.MEM_PRIVATE) {
            typeStr += " private";
        }
        typeStr = typeStr.trim();

        String protStr = "";
        if ((mbi.allocationProtect.intValue() & WinNT.PAGE_EXECUTE) == Kernel32.PAGE_EXECUTE) {
            protStr += " execute";
        }
        if ((mbi.allocationProtect.intValue() & WinNT.PAGE_EXECUTE_READ) == Kernel32.PAGE_EXECUTE_READ) {
            protStr += " execute_read";
        }
        if ((mbi.allocationProtect.intValue() & WinNT.PAGE_EXECUTE_READWRITE) == Kernel32.PAGE_EXECUTE_READWRITE) {
            protStr += " execute_readwrite";
        }
        if ((mbi.allocationProtect.intValue() & WinNT.PAGE_READONLY) == Kernel32.PAGE_READONLY) {
            protStr += " readonly";
        }
        if ((mbi.allocationProtect.intValue() & WinNT.PAGE_READWRITE) == Kernel32.PAGE_READWRITE) {
            protStr += " readwrite";
        }
        if ((mbi.allocationProtect.intValue() & WinNT.PAGE_WRITECOPY) == Kernel32.PAGE_WRITECOPY) {
            protStr += " writecopy";
        }

        protStr = protStr.trim();
        System.out.println("State:" + stateStr);
        System.out.println("Type:" + typeStr);
        System.out.println("Protect:" + protStr);
        System.out.println("baseAddress:" + mbi.baseAddress);
        System.out.println("========================");
    }

    private static byte[] mergeArrays(byte[] one, byte[] two) {
        byte[] combined = new byte[one.length + two.length];

        System.arraycopy(one, 0, combined, 0, one.length);
        System.arraycopy(two, 0, combined, one.length, two.length);
        return combined;
    }

    public static Map<String, Character> getDriveMappings() {
        Map<String, Character> ret = new HashMap<>();
        for (char d = 'A'; d <= 'Z'; d++) {
            char[] buf = new char[1024];
            int len = Kernel32.INSTANCE.QueryDosDevice(d + ":", buf, buf.length).intValue();
            String tar = new String(buf, 0, len);
            tar = tar.trim();
            if (!"".equals(tar)) {
                ret.put(tar, d);
            }
        }
        return ret;
    }

    public static String ntPathToWin32(String path) {
        for (String dp : driveMappings.keySet()) {
            if (path.startsWith(dp)) {
                return driveMappings.get(dp) + ":" + path.substring(dp.length());
            }
        }
        return path;
    }
    private static final Map<String, Character> driveMappings = getDriveMappings();

    public static boolean drawIcon(BufferedImage ret, WinDef.HICON hIcon, int diFlags) {

        WinDef.HDC hdcScreen = User32.INSTANCE.GetDC(null);
        WinDef.HDC hdcMem = Gdi32.INSTANCE.CreateCompatibleDC(hdcScreen);

        WinDef.HBITMAP bitmap = Gdi32.INSTANCE.CreateCompatibleBitmap(hdcScreen, ret.getWidth(), ret.getHeight());

        WinNT.HANDLE hbmOld = Gdi32.INSTANCE.SelectObject(hdcMem, bitmap);

        WinNT.HANDLE hBrush = Gdi32.INSTANCE.CreateSolidBrush(new WinDef.DWORD(0xffffff));
        WinDef.RECT rect = new WinDef.RECT();
        rect.left = 0;
        rect.top = 0;
        rect.right = ret.getWidth();
        rect.bottom = ret.getHeight();
        User32.INSTANCE.FillRect(hdcMem, rect, hBrush);
        Gdi32.INSTANCE.DeleteObject(hBrush);

        boolean ok = User32.INSTANCE.DrawIconEx(hdcMem, 0, 0, hIcon, ret.getWidth(), ret.getHeight(), new WinDef.UINT(0), new WinDef.HBRUSH(Pointer.NULL), diFlags);

        if (!ok) {
            return false;
        }

        for (int x = 0; x < ret.getWidth(); x++) {
            for (int y = 0; y < ret.getHeight(); y++) {
                int rgb = Gdi32.INSTANCE.GetPixel(hdcMem, x, y).intValue();
                int r = (rgb >> 16) & 0xff;
                int g = (rgb >> 8) & 0xff;
                int b = (rgb) & 0xff;
                rgb = (b << 16) + (g << 8) + r;
                ret.setRGB(x, y, rgb);
            }
        }

        Gdi32.INSTANCE.SelectObject(hdcMem, hbmOld);
        Gdi32.INSTANCE.DeleteObject(bitmap);
        Gdi32.INSTANCE.DeleteDC(hdcMem);
        User32.INSTANCE.ReleaseDC(null, hdcScreen);
        return true;
    }

    private static void applyMask(BufferedImage image, BufferedImage mask) {
        int width = image.getWidth();
        int height = image.getHeight();
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                int masked = mask.getRGB(x, y);
                int alpha = 255 - (masked & 0xff);
                if (alpha != 0 && alpha != 255) {
                    System.out.println("a=" + alpha);
                }
                int rgb = image.getRGB(x, y);
                int r = (rgb >> 16) & 0xff;
                int g = (rgb >> 8) & 0xff;
                int b = (rgb) & 0xff;
                r = r * alpha / 255;
                g = g * alpha / 255;
                b = b * alpha / 255;

                rgb = (r << 16) + (g << 8) + b;

                rgb = (alpha << 24) | rgb;
                image.setRGB(x, y, rgb);
            }
        }
    }

    public static BufferedImage iconToImage(WinDef.HICON hIcon) {
        ICONINFO info = new ICONINFO();
        boolean ok = User32.INSTANCE.GetIconInfo(hIcon, info);
        if (!ok) {
            return null;
        }
        BufferedImage ret = null;
        BITMAP bmp = new BITMAP();
        if (info.hbmColor != null) {
            int nWrittenBytes = Gdi32.INSTANCE.GetObject(info.hbmColor, bmp.size(), bmp);
            if (nWrittenBytes > 0) {
                ret = new BufferedImage(bmp.bmWidth.intValue(), bmp.bmHeight.intValue(), BufferedImage.TYPE_INT_ARGB);
            }
        } else if (info.hbmMask != null) {
            int nWrittenBytes = Gdi32.INSTANCE.GetObject(info.hbmMask, bmp.size(), bmp);
            if (nWrittenBytes > 0) {
                ret = new BufferedImage(bmp.bmWidth.intValue(), bmp.bmHeight.intValue() / 2, BufferedImage.TYPE_INT_ARGB);
            }
        }
        if (ret == null) {
            return ret;
        }

        if (info.hbmColor != null) {
            Gdi32.INSTANCE.DeleteObject(info.hbmColor);
        }
        if (info.hbmMask != null) {
            Gdi32.INSTANCE.DeleteObject(info.hbmMask);
        }

        drawIcon(ret, hIcon, User32.DI_NORMAL);
        BufferedImage mask = new BufferedImage(ret.getWidth(), ret.getHeight(), BufferedImage.TYPE_INT_RGB);
        drawIcon(mask, hIcon, User32.DI_MASK);
        applyMask(ret, mask);

        return ret;
    }

    public static BufferedImage getShellIcon(String path) {
        SHFILEINFO fi = new SHFILEINFO();
        BaseTSD.DWORD_PTR r = Shell32.INSTANCE.SHGetFileInfo(path, 0, fi, fi.size(), Shell32.SHGFI_ICON | Shell32.SHGFI_SMALLICON);
        if (r.intValue() == 0) {
            return null;
        }
        return iconToImage(fi.hIcon);
    }

    public static BufferedImage extractExeIcon(String fullExeFile, int index, boolean large) {
        PointerByReference iconsLargeRef = new PointerByReference();
        PointerByReference iconsSmallRef = new PointerByReference();
        int cnt = Shell32.INSTANCE.ExtractIconEx(fullExeFile, index, iconsLargeRef, iconsSmallRef, new WinDef.UINT(1)).intValue();
        if (cnt == 0) {
            return null;
        }
        Pointer iconsLarge = iconsLargeRef.getPointer();
        Pointer iconsSmall = iconsSmallRef.getPointer();

        WinDef.HICON icon;
        if (large) {
            icon = new WinDef.HICON(iconsLarge.getPointer(0));
        } else {
            icon = new WinDef.HICON(iconsSmall.getPointer(0));
        }

        BufferedImage ic = iconToImage(icon);

        User32.INSTANCE.DestroyIcon(icon);
        return ic;
    }

    public static List<com.jpexs.process.Process> listProcesses() {
        if (!privAdjusted) {
            adjustPrivileges();
        }
        List<com.jpexs.process.Process> ret = new ArrayList<>();
        WinNT.HANDLE hSnapShot = Kernel32.INSTANCE.CreateToolhelp32Snapshot(new WinDef.DWORD(Kernel32.TH32CS_SNAPPROCESS), new WinDef.DWORD(0));
        PROCESSENTRY32 pe = new PROCESSENTRY32();
        if (Kernel32.INSTANCE.Process32First(hSnapShot, pe)) {
            do {
                int i;
                i = 0;
                for (; i < pe.szExeFile.length; i++) {
                    if (pe.szExeFile[i] == 0) {
                        break;
                    }
                }
                String exeFile = new String(pe.szExeFile, 0, i);
                char[] outputnames = new char[1024];
                WinNT.HANDLE tempProcess = Kernel32.INSTANCE.OpenProcess(Kernel32.PROCESS_QUERY_INFORMATION, false, pe.th32ProcessID);
                if (tempProcess == null) {
                    continue;
                }

                try {
                    int rn = Psapi.INSTANCE.GetProcessImageFileNameW(tempProcess, outputnames, 1024);
                    if (rn == 0) {
                        Logger.getLogger(Win32ProcessTools.class.getName()).log(Level.SEVERE, "Can't get EXE path");
                    }
                } catch (Exception | UnsatisfiedLinkError e) {
                }

                try {
                    int rn = Kernel32.INSTANCE.GetProcessImageFileNameW(tempProcess, outputnames, 1024);
                    if (rn == 0) {
                        Logger.getLogger(Win32ProcessTools.class.getName()).log(Level.SEVERE, "Can't get EXE path");
                    }
                } catch (Exception | UnsatisfiedLinkError e) {
                }
                i = 0;
                for (; i < outputnames.length; i++) {
                    if (outputnames[i] == 0) {
                        break;
                    }
                }
                String fullExeFile = new String(outputnames, 0, i);
                fullExeFile = ntPathToWin32(fullExeFile);
                ret.add(new Win32Process(fullExeFile, exeFile, getShellIcon(fullExeFile), pe.th32ProcessID));
            } while (Kernel32.INSTANCE.Process32Next(hSnapShot, pe));
        }
        return ret;
    }

    private static boolean pageReadable(MEMORY_BASIC_INFORMATION mbi) {
        int iUnReadable = 0;
        iUnReadable |= (mbi.state.intValue() == Kernel32.MEM_FREE) ? 1 : 0;
        iUnReadable |= (mbi.state.intValue() == Kernel32.MEM_RESERVE) ? 1 : 0;
        iUnReadable |= (mbi.protect.intValue() & WinNT.PAGE_WRITECOPY);
        iUnReadable |= (mbi.protect.intValue() & WinNT.PAGE_EXECUTE);
        iUnReadable |= (mbi.protect.intValue() & WinNT.PAGE_GUARD);
        iUnReadable |= (mbi.protect.intValue() & WinNT.PAGE_NOACCESS);
        return iUnReadable == 0;
    }

    public static Map<Long, InputStream> findBytesInProcessMemory(ProgressListener progListener, WinDef.DWORD dwProcessID, byte[][] findBytesAll) {
        int maxFindLen = 0;
        for (int i = 0; i < findBytesAll.length; i++) {
            if (findBytesAll[i].length > maxFindLen) {
                maxFindLen = findBytesAll[i].length;
            }
        }
        Map<Long, InputStream> ret = new HashMap<>();
        WinNT.HANDLE hOtherProcess = Kernel32.INSTANCE.OpenProcess(Kernel32.PROCESS_VM_READ | Kernel32.PROCESS_VM_WRITE | Kernel32.PROCESS_QUERY_INFORMATION | Kernel32.PROCESS_VM_OPERATION /*for VirtualProtectEx*/, false, dwProcessID);
        List<MEMORY_BASIC_INFORMATION> pages = getPageRanges(hOtherProcess);
        long totalMemLen = 0;
        int progress = 0;
        for (int pg = 0; pg < pages.size(); pg++) {
            totalMemLen += pages.get(pg).regionSize.longValue();
        }

        long actualPos = 0;
        List<Integer> guardedPages = new ArrayList<>();
        for (int pg = 0; pg < pages.size(); pg++) {
            MEMORY_BASIC_INFORMATION mbi = pages.get(pg);
            if (pageReadable(mbi)) {
                long addr = pointerToAddress(mbi.baseAddress);
                int maxsize = mbi.regionSize.intValue();
                long pos = 0;
                long bufSize = 1024 * 512;
                do {
                    NativeLongByReference bytesReadRef = new NativeLongByReference();
                    Memory buf = new Memory(bufSize);
                    boolean ok = Kernel32.INSTANCE.ReadProcessMemory(hOtherProcess, new Pointer(addr + pos), buf, new NativeLong(bufSize), bytesReadRef);
                    if (!ok) {
                        break;
                    }
                    if (bytesReadRef.getValue().longValue() == 0) {
                        break;
                    }

                    byte[] data = buf.getByteArray(0, bytesReadRef.getValue().intValue());

                    byte[] prevBytes = Arrays.copyOfRange(data, data.length - maxFindLen, data.length);
                    byte[] dataPlusPrev = mergeArrays(prevBytes, data);
                    loopi:
                    for (int i = 0; i < dataPlusPrev.length - maxFindLen; i++) {
                        loopk:
                        for (int k = 0; k < findBytesAll.length; k++) {
                            if (dataPlusPrev[i] == findBytesAll[k][0]) {
                                for (int p = 1; p < findBytesAll[k].length; p++) {
                                    if (dataPlusPrev[i + p] != findBytesAll[k][p]) {
                                        continue loopk;
                                    }
                                }
                                long dataAddr = (long) (addr + pos - prevBytes.length + i);
                                ret.put(dataAddr, new ProcessMemoryInputStream(pages, hOtherProcess, pg, pos - prevBytes.length + i));
                            }
                        }

                    }
                    pos += bytesReadRef.getValue().longValue();
                    if (progListener != null) {
                        int newprogress = Math.round((actualPos + pos) * 100 / totalMemLen);
                        if (newprogress != progress) {
                            progress = newprogress;
                            progListener.progress(progress);
                        }
                    }
                    if (pos + bufSize >= maxsize) {
                        bufSize = maxsize - pos;
                    }
                } while (bufSize > 0);
            } else {
                if (hasGuard(mbi)) {
                    if (unsetGuard(hOtherProcess, mbi)) {
                        guardedPages.add(pg);
                        pg--;
                        continue;
                    }
                }
            }
            actualPos += mbi.regionSize.longValue();
            int newprogress = Math.round(actualPos * 100 / totalMemLen);
            if (newprogress != progress) {
                progress = newprogress;
                progListener.progress(progress);
            }
        }

        //Set PAGE_GUARD back again
        for (int pg : guardedPages) {
            setGuard(hOtherProcess, pages.get(pg));
        }
        return ret;
    }

    private static boolean hasGuard(MEMORY_BASIC_INFORMATION mbi) {
        return (mbi.protect.intValue() & WinNT.PAGE_GUARD) == WinNT.PAGE_GUARD;
    }

    private static boolean unsetGuard(HANDLE hOtherProcess, MEMORY_BASIC_INFORMATION mbi) {
        if (!hasGuard(mbi)) {
            return true;
        }
        int oldProt = mbi.protect.intValue();
        int newProt = oldProt - WinNT.PAGE_GUARD;
        IntByReference oldProtRef = new IntByReference();
        boolean ok = Kernel32.INSTANCE.VirtualProtectEx(hOtherProcess, new WinDef.LPVOID(pointerToAddress(mbi.baseAddress)), mbi.regionSize, newProt, oldProtRef);
        if (ok) {
            mbi.protect = new NativeLong(newProt);
            return true;
        }
        return false;
    }

    private static boolean setGuard(HANDLE hOtherProcess, MEMORY_BASIC_INFORMATION mbi) {
        if (hasGuard(mbi)) {
            return true;
        }
        int oldProt = mbi.protect.intValue();
        int newProt = oldProt | WinNT.PAGE_GUARD;
        IntByReference oldProtRef = new IntByReference();
        boolean ok = Kernel32.INSTANCE.VirtualProtectEx(hOtherProcess, new WinDef.LPVOID(pointerToAddress(mbi.baseAddress)), mbi.regionSize, newProt, oldProtRef);
        if (ok) {
            mbi.protect = new NativeLong(newProt);
            return true;
        }
        return false;
    }
    private static boolean privAdjusted = false;

    public static boolean adjustPrivileges() {

        WinNT.TOKEN_PRIVILEGES tp = new WinNT.TOKEN_PRIVILEGES(1);
        WinNT.TOKEN_PRIVILEGES oldtp = new WinNT.TOKEN_PRIVILEGES(1);
        WinNT.LUID luid = new WinNT.LUID();
        WinNT.HANDLEByReference hTokenRef = new WinNT.HANDLEByReference();
        if (!Advapi32.INSTANCE.OpenProcessToken(Kernel32.INSTANCE.GetCurrentProcess(), WinNT.TOKEN_ADJUST_PRIVILEGES | WinNT.TOKEN_QUERY, hTokenRef)) {
            return false;
        }
        WinNT.HANDLE hToken = hTokenRef.getValue();
        if (!Advapi32.INSTANCE.LookupPrivilegeValue(null, WinNT.SE_DEBUG_NAME, luid)) {
            Kernel32.INSTANCE.CloseHandle(hToken);
            return false;
        }

        tp.PrivilegeCount = new WinDef.DWORD(1);
        tp.Privileges = new WinNT.LUID_AND_ATTRIBUTES[1];
        tp.Privileges[0] = new WinNT.LUID_AND_ATTRIBUTES(luid, new WinDef.DWORD(WinNT.SE_PRIVILEGE_ENABLED));

        IntByReference retSize = new IntByReference(0);
        if (!Advapi32.INSTANCE.AdjustTokenPrivileges(hToken, false, tp, tp.size(), oldtp, retSize)) {
            Kernel32.INSTANCE.CloseHandle(hToken);
            return false;
        }
        Kernel32.INSTANCE.CloseHandle(hToken);
        privAdjusted = true;
        return true;
    }

    private static class ProcessMemoryInputStream extends InputStream {

        private final List<MEMORY_BASIC_INFORMATION> pages;
        private int currentPage = 0;
        private long pagePos = 0;
        private static final int BUFFER_SIZE = 1024 * 512;
        private byte[] buf;
        private int bufPos;
        private final HANDLE hOtherProcess;

        public ProcessMemoryInputStream(List<MEMORY_BASIC_INFORMATION> pages, HANDLE hOtherProcess, int currentPage, long pagePos) {
            this.pages = pages;
            this.hOtherProcess = hOtherProcess;
            this.currentPage = currentPage;
            this.pagePos = pagePos;
        }

        private boolean readNext() throws IOException {
            MEMORY_BASIC_INFORMATION mbi = pages.get(currentPage);
            if (!pageReadable(mbi)) {
                if (hasGuard(mbi)) {
                    if (unsetGuard(hOtherProcess, mbi)) {
                        boolean ret = readNext();
                        setGuard(hOtherProcess, mbi);
                        return ret;
                    }
                }
                if (currentPage + 1 < pages.size()) {
                    pagePos = 0;
                    currentPage++;
                    return readNext();
                }
                return false;
            }
            long addr = pointerToAddress(mbi.baseAddress);
            int maxsize = mbi.regionSize.intValue();
            NativeLongByReference bytesReadRef = new NativeLongByReference();
            Memory membuf = new Memory(BUFFER_SIZE);
            NativeLong bufSize = new NativeLong(BUFFER_SIZE);
            if (pagePos + bufSize.longValue() > maxsize) {
                bufSize.setValue(maxsize - pagePos);
            }
            if (bufSize.longValue() == 0) {
                if (currentPage + 1 < pages.size()) {
                    pagePos = 0;
                    currentPage++;
                    return readNext();
                }
                return false;
            }
            boolean ok = Kernel32.INSTANCE.ReadProcessMemory(hOtherProcess, new Pointer(addr + pagePos), membuf, bufSize, bytesReadRef);
            if (!ok) {
                throw new IOException("Cannot read memory");
            }
            if (bytesReadRef.getValue().longValue() == 0) {
                return readNext();
            }
            pagePos += bytesReadRef.getValue().longValue();

            buf = membuf.getByteArray(0, bytesReadRef.getValue().intValue());
            return true;
        }

        @Override
        public int read() throws IOException {
            if ((buf == null) || (bufPos >= buf.length)) {
                if (buf != null) {
                    bufPos = 0;
                }
                if (!readNext()) {
                    return -1;
                }
            }

            return buf[bufPos++] & 0xff;
        }
    }
}
