{
    EmulationWindow используется для создания окна, в котором пользователь
    смотрит экран выполняемой Малик Эмулятором программы и взаимодействует с
    этой программой.
    Этот исходный текст является частью Малик Эмулятора.

    Следующие файлы используются этим исходным текстом:
        emulationwindow.lfm
    На них так же распространяются те же права, как и на этот исходный текст.

    Copyright © 2016–2017, 2019–2022 Малик Разработчик

    Малик Эмулятор – свободная программа: вы можете перераспространять её и/или
    изменять её на условиях Стандартной общественной лицензии GNU в том виде,
    в каком она была опубликована Фондом свободного программного обеспечения;
    либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

    Малик Эмулятор распространяется в надежде, что он может быть полезен,
    но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
    или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЁННЫХ ЦЕЛЕЙ. Подробнее см. в Стандартной
    общественной лицензии GNU.

    Вы должны были получить копию Стандартной общественной лицензии GNU
    вместе с этой программой. Если это не так, см.
    <http://www.gnu.org/licenses/>.
}

unit EmulationWindow;

{$MODE DELPHI}
{$ASMMODE INTEL}

interface

uses
    Windows,
    Classes,
    SysUtils,
    Controls,
    Forms,
    Graphics,
    ComCtrls,
    ExtCtrls,
    LMessages,
    LCLType,
    Buttons,
    Lang,
    IOStreams,
    FileIO,
    Zlib,
    Images,
    UFonts,
    Manifests,
    Timers,
    Players,
    EmulMalik,
    EmulConstants,
    EmulProgrammes,
    PixelGraphicScaling,
    PixelGraphicNearNeighbour,
    PixelGraphicSimpleScaling,
    PixelGraphicHighQualityScaling,
    PixelGraphicPhotographicScaling,
    EmulThemes,
    EmulatorInterfaces,
    DisassemblerWindow;

{%region public }
const
    SYSTEM_OBJECT_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C151C}';

type
    SystemObject = interface;
    FontsNotFoundException = class;
    RecordingThread = class;
    EmulationObject = class;
    EmulationRefCountObject = class;
    TEmulationForm = class;
    EmulationOfFileStream = class;
    EmulationOfFileEnumeration = class;
    EmulationOfUnicodeFont = class;
    EmulationOfPlayer = class;
    EmulationOfPlayerPCM = class;
    EmulationOfPlayerMIDI = class;

    PMalikDataDescriptor = ^MalikDataDescriptor;
    PMalikFileAttributes = ^MalikFileAttributes;
    PMalikFileInfo = ^MalikFileInfo;
    PMalikGraphicBuffer = ^MalikGraphicBuffer;
    PMalikGraphicBufferWithAlpha = ^MalikGraphicBufferWithAlpha;
    PMalikGUIElementDraw = ^MalikGUIElementDraw;
    PMalikStretchDraw = ^MalikStretchDraw;
    PMalikTextDraw = ^MalikTextDraw;

    MalikDataDescriptor = packed record
        count: int;
        address: int;
    end;

    MalikFileAttributes = packed record
        creationTime: long;
        lastAccessTime: long;
        lastWriteTime: long;
        attributes: int;
        reserved: int;
    end;

    MalikFileInfo = packed record
        attributes: MalikFileAttributes;
        size: long;
        nameLength: int;
        nameAddress: int;
    end;

    MalikGraphicBuffer = packed record
        base: int;
        scanline: int;
        width: short;
        height: short;
    end;

    MalikGraphicBufferWithAlpha = packed record
    case int of
    0: (base: int;
        scanline: int;
        width: short;
        height: short;
        supportsAlpha: boolean);
    1: (buffer: MalikGraphicBuffer;
        reserved: int);
    end;

    MalikGUIElementDraw = packed record
        dst: MalikGraphicBufferWithAlpha;
        left: short;
        top: short;
        width: short;
        height: short;
        element: int;
    end;

    MalikStretchDraw = packed record
        dst: MalikGraphicBufferWithAlpha;
        left: short;
        top: short;
        width: short;
        height: short;
        src: MalikGraphicBufferWithAlpha;
        transform: int;
    end;

    MalikTextDraw = packed record
        dst: MalikGraphicBufferWithAlpha;
        handle: int;
        style: int;
        x: short;
        y: short;
        color: int;
        charsCount: int;
        charsAddress: int;
    end;

    SystemObject_Array1d = packed array of SystemObject;

    SystemObject = interface(_Interface) [SYSTEM_OBJECT_GUID]
        function syscall(func, param: int): long;
    end;

    FontsNotFoundException = class(Exception)
    public
        constructor create(); overload;
        constructor create(const message: AnsiString); overload;
    end;

    RecordingThread = class(Thread)
    private
        isBusy: boolean;
        isStop: boolean;
        isPause: boolean;
        isClosed: boolean;
        pauseTime: long;
        canvasWidth: int;
        canvasHeight: int;
        currFrameTime: long;
        nextFrameTime: long;
        prevFramePixels: int_Array1d;
        currFramePixels: int_Array1d;
        nextFramePixels: int_Array1d;
        pngData: byte_Array1d;
        frameEvent: THandle;
        apngFileName: AnsiString;
        function isRowsEquals(rowIndex: int): boolean;
        function isColsEquals(colIndex, rowOffset, rowLength: int): boolean;
        function isNextFrameEquals(nextFramePixels: PIntArray; nextFrameScanline, nextFrameWidth, nextFrameHeight: int): boolean;
        function toPNGData(left, top, width, height: int; opaque: boolean): int;
    public
        constructor create(const apngFileName: AnsiString; mainFramePixels: PIntArray; mainFrameScanline, mainFrameWidth, mainFrameHeight: int);
        destructor destroy; override;
        procedure run(); override;
        procedure addFrame(nextFramePixels: PIntArray; nextFrameScanline, nextFrameWidth, nextFrameHeight: int);
        procedure pause();
        procedure stop();
        function isPaused(): boolean;

    private
        class procedure writePNGChunk(pngChunk: ByteArrayOutputStream; crc: Checksum32; dataStream: DataOutput); static;
    end;

    EmulationObject = class(_Object)
    protected
        TEmulationForm_self: TEmulationForm;
    public
        constructor create(TEmulationForm_self: TEmulationForm);
    end;

    EmulationRefCountObject = class(RefCountInterfacedObject)
    protected
        TEmulationForm_self: TEmulationForm;
    public
        constructor create(TEmulationForm_self: TEmulationForm);
    end;

    TEmulationForm = class(TForm, _Interface, Runnable, GraphicListener, TimerHandler, PlayerPCMListener, PlayerMIDIListener, ProcessorListener, SystemObject, EmulationWindowInterface)
        fps: TTimer;
        panelView: TPanel;
        sbtnViewRotateLeft: TSpeedButton;
        sbtnViewRotateRight: TSpeedButton;
        sbtnViewZoomIn: TSpeedButton;
        sbtnViewZoomOut: TSpeedButton;
        panelMonitor: TPanel;
        sbtnMonitorAdjustSize: TSpeedButton;
        sbtnMonitorFullScreen: TSpeedButton;
        sbtnMonitorRecordStart: TSpeedButton;
        sbtnMonitorRecordStop: TSpeedButton;
        status: TStatusBar;
        disassembler: TDisassemblerForm;
        procedure formKeyPressed(sender: TObject; var key: Word; shift: TShiftState);
        procedure formKeyReleased(sender: TObject; var key: Word; shift: TShiftState);
        procedure formUTF8KeyPressed(sender: TObject; var utf8Key: TUTF8Char);
        procedure formPointerPressed(sender: TObject; button: TMouseButton; shift: TShiftState; x, y: integer);
        procedure formPointerDragged(sender: TObject; shift: TShiftState; x, y: integer);
        procedure formPointerReleased(sender: TObject; button: TMouseButton; shift: TShiftState; x, y: integer);
        procedure formScrollingWheelDown(sender: TObject; shift: TShiftState; pointerCoordinates: TPoint; var handled: boolean);
        procedure formScrollingWheelUp(sender: TObject; shift: TShiftState; pointerCoordinates: TPoint; var handled: boolean);
        procedure formWindowStateChange(sender: TObject);
        procedure formResize(sender: TObject);
        procedure formPaint(sender: TObject);
        procedure formClose(sender: TObject; var action: TCloseAction);
        procedure fpsTimer(sender: TObject);
        procedure buttonClick(sender: TObject);
    strict private
        owner: MainWindowInterface;
        runningProgrammeID: AnsiString;
        runningProgrammeInfo: ProgrammeInfo;
        ptimer: Timer;
        recording: RecordingThread;
        processor: MalikProcessor;
        context: MalikDebugContext;
        contextID: int;
        frames: int;
        windowHandle: int;
        closeOnTerminate: boolean;
        nowDebugging: boolean;
        debug: boolean;
        pause: boolean;
        suspend: boolean;
        terminated: boolean;
        modeChanging: boolean;
        hostPressed: boolean;
        keyPressed: int;
        normalScreenZoom: int;
        prevWindowState: TWindowState;
        data: long_Array1d;
        procedure windowActivate(active: boolean);
        procedure processorBegin();
        procedure processorEnd();
        procedure debugBegin();
        procedure debugEnd();
        procedure recalculateStatusPanelsWidth();
        procedure screenUpdate(fromProgramme: boolean);
        function playerPCMCreate(signalParameters, blockLength: int): int;
        function playerPCMLoadBlock(playerHandle, descriptorAddress: int): int;
        function playerPCMPlayback(playerHandle, command: int): int;
        function playerPCMGetState(playerHandle: int): int;
        function playerPCMSetPosition(playerHandle, position: int): int;
        function playerPCMGetPosition(playerHandle: int): long;
        function playerPCMVolume(playerHandle, volume: int): int;
        function playerPCMDestroy(playerHandle: int): int;
        function playerMIDICreate(): int;
        function playerMIDILoadBlock(playerHandle, descriptorAddress: int): int;
        function playerMIDIPlayback(playerHandle, command: int): int;
        function playerMIDIGetState(playerHandle: int): int;
        function playerMIDISetPosition(playerHandle, position: int): int;
        function playerMIDIGetPosition(playerHandle: int): long;
        function playerMIDIVolume(playerHandle, volume: int): int;
        function playerMIDIDestroy(playerHandle: int): int;
        function getPlayerPCM(playerHandle: int): PlayerPCM;
        function getPlayerMIDI(playerHandle: int): PlayerMIDI;
        function readNullTerminatedUnicodeString(address: int): UnicodeString;
    strict private
        guiTheme: Theme;
        scalingAlgorithm: PixelGraphicScalingAlgorithm;
        maximumFrames: int;
        renderInProgress: boolean;
        resolutionChanged: boolean;
        sourceImagePixels: PIntArray;
        sourceImageScanline: int;
        sourceImageWidth: int;
        sourceImageHeight: int;
        sourceImageBitmap: TBitmap;
        rotatedImageBitmap: TBitmap;
        rotatedImagePixels: PIntArray;
        rotatedImageScanline: int;
        scaledImageBitmap: TBitmap;
        scaledImagePixels: PIntArray;
        outputImage: TBitmap;
        screenImageAddress: int;
        screenImagePixels: PIntArray;
        screenImageScanline: int;
        screenImageWidth: int;
        screenImageHeight: int;
        screenRotate: int;
        screenZoom: int;
        screenLeft: int;
        screenTop: int;
        outputGraphicWidth: int;
        outputGraphicHeight: int;
        outputGraphicScanline: int;
        outputGraphicPixels: PIntArray;
        outputGraphicWithAlpha: boolean;
        inputGraphicWithAlpha: boolean;
        procedure createSystemMenu();
        procedure startRecording();
        procedure stopRecording();
        procedure zoomIn();
        procedure zoomOut();
        procedure rotateLeft();
        procedure rotateRight();
        procedure adjustMonitorSize();
        procedure switchFullScreenMode();
        procedure switchToolBarsVisible();
        procedure setScaledImageSize(width, height: int);
        procedure setFullScreenMode(fullScreen: boolean);
        procedure setMonitorSize(width, height: int);
        procedure setOutputAreaBounds(left, top, width, height: int);
        function isOutputGraphicInvalid(): boolean;
        function isFullScreenMode(): boolean;
        function getMonitorWidth(): int;
        function getMonitorHeight(): int;
        function getOutputAreaLeft(): int;
        function getOutputAreaTop(): int;
        function getOutputAreaWidth(): int;
        function getOutputAreaHeight(): int;
        function getVirtualCoordinates(x, y: int; virtualCoordinates: PPoint): boolean;
        function fileOpen(fileNameAddress, mode: int): int;
        function fileDelete(fileNameAddress: int): int;
        function fileFind(fileNameAddress, descriptorAddress: int): int;
        function fileMove(sourceFileNameAddress, destinationFileNameAddress: int): int;
        function fileReadAttr(fileNameAddress, descriptorAddress: int): int;
        function fileWriteAttr(fileNameAddress, descriptorAddress: int): int;
        function directoryCreate(directoryNameAddress: int): int;
        function directoryDelete(directoryNameAddress: int): int;
        function screenUpdateQuery(): int;
        function screenReadBuffer(): int;
        function screenGetBuffer(descriptorAddress: int): int;
        function screenSetBuffer(descriptorAddress: int): int;
        function screenGetGUIElementMinSizes(element: int): int;
        function screenDrawGUIElement(descriptorAddress: int): int;
        function screenGetSystemColor(index: int): int;
        function screenStretchDraw(descriptorAddress: int): int;
        function screenCharsGetWidth(descriptorAddress: int): int;
        function screenCharsOutput(descriptorAddress: int): int;
        function screenFontInstall(fontNameAddress, fileNameAddress: int): int;
        function screenFontUninstall(fontHandle: int): int;
        function timerSetInterval(milliseconds: long): int;
        function timerGetRemainingTime(): long;
        function inoutGetKeyboardLightState(): int;
    strict private
        systemObjects: SystemObject_Array1d;
        systemObjectsCount: int;
        function addSystemObject(obj: SystemObject): int;
        function nextSystemObject(handle: int; objClass: TClass): int;
        function handleSystemObject(func: int; param: long): long;
    private
        function getSystemObject(handle: int): SystemObject;
        function closeSystemObject(handle: int; objClass: TClass): boolean;
        function malikFileNameToOS(const fileName: UnicodeString): UnicodeString;
        function malikFileCheckAccess(const fileName: UnicodeString; forWrite: boolean): boolean;
        procedure writeFileInfoToProcessor(descriptorAddress: int; const findData: Windows.WIN32_FIND_DATAW);
    private
        procedure postMessage(msg: int); overload;
        procedure postMessage(msg, wparam, lparam: int); overload;
        function sendMessage(msg: int): int; overload;
        function sendMessage(msg, wparam, lparam: int): int; overload;
    protected
        procedure wndProc(var theMessage: TLMessage); override;
    public
        constructor create(theOwner: TComponent); override;
        destructor destroy; override;
        { _Interface }
        function getClass(): _Class;
        function asObject(): TObject;
        { Runnable }
        procedure run();
        { GraphicListener }
        procedure putPixel(x, y, argb: int);
        { TimerHandler }
        procedure timerHandle(timer: Timer; millisecondsElapsed: long);
        { PlayerPCMListener }
        procedure loadPCMBlock(player: PlayerPCM; blockIndex: int);
        { PlayerMIDIListener }
        procedure endMIDITrack(player: PlayerMIDI);
        { ProcessorListener }
        procedure programmePause();
        procedure programmeResume();
        procedure programmeTerminated();
        procedure programmeBreakpoint();
        procedure instructionExecuting(contextID: int);
        function getCurrentUTCOffset(): int;
        function getCurrentUTCTime(): long;
        function getMilliseconds(): long;
        function syscall(func: int; argument: long): long; overload;
        { SystemObject }
        function syscall(func, param: int): long; overload;
        { EmulationWindowInterface }
        procedure showEmulationWindow();
        procedure showDisassemblerWindow();
        procedure setDebug(debug: boolean);
        function isNowDebugging(): boolean;
        function getMalikProcessor(): MalikProcessor;
        function getMainWindow(): MainWindowInterface;
        function getProgrammeDirectory(): AnsiString;
        { Собственные методы }
        procedure setProgramme(const id: AnsiString; info: ProgrammeInfo);
        procedure runProgramme();
        procedure showExecutionStatus(const statusText: AnsiString);
        procedure showRecordingStatus(const statusText: AnsiString);
        procedure showScreenZoomStatus(screenZoom: int);
        procedure showFramesPerSecondStatus(fps: int);

    private class var
        STR_TOOLBARS: UnicodeString;
    protected const
        ID_TOOLBARS = int(1);
    protected const
        DIRECTORY_FONTS = 'fonts';
        DIRECTORY_VIDEO = 'video';
    protected const
        EM_PROCESSOR_BEGIN = int(LM_USER + 1);
        EM_PROCESSOR_END = int(LM_USER + 2);
        EM_DEBUG_BEGIN = int(LM_USER + 3);
        EM_DEBUG_END = int(LM_USER + 4);
        EM_SCREEN_UPDATE = int(LM_USER + 5);
        EM_SYSCALL = int(LM_USER + $400);
        EM_PLAYER_PCM_CREATE = int(EM_SYSCALL + $0030);
        EM_PLAYER_PCM_LOAD_BLOCK = int(EM_SYSCALL + $0031);
        EM_PLAYER_PCM_PLAYBACK_CONTROL = int(EM_SYSCALL + $0032);
        EM_PLAYER_PCM_GET_STATE = int(EM_SYSCALL + $0033);
        EM_PLAYER_PCM_BLOCK_SEEK = int(EM_SYSCALL + $0034);
        EM_PLAYER_PCM_GET_BLOCK_INDEX = int(EM_SYSCALL + $0035);
        EM_PLAYER_PCM_VOLUME_CONTROL = int(EM_SYSCALL + $0036);
        EM_PLAYER_PCM_DESTROY = int(EM_SYSCALL + $0037);
        EM_PLAYER_MIDI_CREATE = int(EM_SYSCALL + $0038);
        EM_PLAYER_MIDI_LOAD_BLOCK = int(EM_SYSCALL + $0039);
        EM_PLAYER_MIDI_PLAYBACK_CONTROL = int(EM_SYSCALL + $003a);
        EM_PLAYER_MIDI_GET_STATE = int(EM_SYSCALL + $003b);
        EM_PLAYER_MIDI_BLOCK_SEEK = int(EM_SYSCALL + $003c);
        EM_PLAYER_MIDI_GET_BLOCK_INDEX = int(EM_SYSCALL + $003d);
        EM_PLAYER_MIDI_VOLUME_CONTROL = int(EM_SYSCALL + $003e);
        EM_PLAYER_MIDI_DESTROY = int(EM_SYSCALL + $003f);
    protected const
        TRANSFORM_NONE = int(0);
        TRANSFORM_ROTATE_90 = int(1);
        TRANSFORM_ROTATE_180 = int(2);
        TRANSFORM_ROTATE_270 = int(3);
        TRANSFORM_MIRROR = int(4);
        TRANSFORM_MIRROR_ROTATE_90 = int(5);
        TRANSFORM_MIRROR_ROTATE_180 = int(6);
        TRANSFORM_MIRROR_ROTATE_270 = int(7);
    end;

    EmulationOfFileStream = class(EmulationRefCountObject, SystemObject)
    strict private
        invalidHandle: boolean;
        fileInput: Input;
        fileOutput: Output;
        function write(param: int): int;
        function read(param: int): int;
        function seek(param: int): int;
        function size(): long;
        function truncate(): long;
    public
        constructor create(TEmulationForm_self: TEmulationForm; const fileName: UnicodeString; mode: int);
        function syscall(func, param: int): long;
        function isInvalidHandle(): boolean;
    end;

    EmulationOfFileEnumeration = class(EmulationRefCountObject, SystemObject)
    strict private
        handle: int;
        directory: boolean;
        fileName: UnicodeString;
        function findNext(param: int): int;
    public
        constructor create(TEmulationForm_self: TEmulationForm; handle: int; directory: boolean; const fileName: UnicodeString);
        destructor destroy; override;
        function syscall(func, param: int): long;
    end;

    EmulationOfUnicodeFont = class(EmulationRefCountObject, SystemObject)
    strict private
        font: UnicodeFont;
        function getNameAndLength(nameDestAddress: int): int;
        function getFontInfo(): int;
        function getCharSupport(code: int): int;
    public
        constructor create(TEmulationForm_self: TEmulationForm; const fontName, fileName: AnsiString);
        destructor destroy; override;
        function syscall(func, param: int): long;
        function getFont(): UnicodeFont;
    end;

    EmulationOfPlayer = class(EmulationRefCountObject, SystemObject)
    private
        handle: int;
        soundPlayer: Player;
    public
        constructor create(TEmulationForm_self: TEmulationForm; soundPlayer: Player);
        destructor destroy; override;
        function syscall(func, param: int): long;
    end;

    EmulationOfPlayerPCM = class(EmulationOfPlayer)
    public
        constructor create(TEmulationForm_self: TEmulationForm; soundPlayer: PlayerPCM);
    end;

    EmulationOfPlayerMIDI = class(EmulationOfPlayer)
    public
        constructor create(TEmulationForm_self: TEmulationForm; soundPlayer: PlayerMIDI);
    end;
{%endregion}

implementation

{$R *.LFM}

{%region private }
var
    internalCounter: int = $007fffff;
{%endregion}

{%region routine }
    function getInternalCounterValue(): int; assembler; nostackframe;
    asm
                mov     eax, $00000001
                lock xadd dword[internalCounter], eax
                lea     eax, [eax+$01]
    end;

    function SystemObject_Array1d_create(length: int): SystemObject_Array1d;
    begin
        setLength(result, length);
    end;

    procedure arraycopy(const src: SystemObject_Array1d; srcOffset: int; const dst: SystemObject_Array1d; dstOffset: int; length: int); overload;
    var
        lim: int;
        len: int;
        i: int;
    begin
        lim := srcOffset + length;
        len := System.length(src);
        if (lim > len) or (lim < srcOffset) or (srcOffset < 0) or (srcOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        lim := dstOffset + length;
        len := System.length(dst);
        if (lim > len) or (lim < dstOffset) or (dstOffset < 0) or (dstOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        if (src = dst) and (srcOffset < dstOffset) then begin
            for i := length - 1 downto 0 do begin
                dst[dstOffset + i] := src[srcOffset + i];
            end;
        end else begin
            for i := 0 to length - 1 do begin
                dst[dstOffset + i] := src[srcOffset + i];
            end;
        end;
    end;

    procedure intArrayCopy(src: PIntArray; srcOffset: int; dst: PIntArray; dstOffset: int; length: int); overload; stdcall; assembler;
    asm
                mov     ecx, [length]
                cmp     ecx, $00
                jg      @0
                leave
                ret     $14
        @0:     push    esi
                push    edi
                mov     eax, [src]
                mov     edx, [srcOffset]
                lea     esi, [eax+edx*4]
                mov     eax, [dst]
                mov     edx, [dstOffset]
                lea     edi, [eax+edx*4]
                cld
                rep movsd
                pop     edi
                pop     esi
    end;

    procedure opaquePixelsArrayCopy(src: PIntArray; srcOffset: int; dst: PIntArray; dstOffset: int; length: int); overload; stdcall; assembler;
    asm
                mov     ecx, [length]
                cmp     ecx, $00
                jg      @0
                leave
                ret     $14
        @0:     push    esi
                push    edi
                mov     eax, [src]
                mov     edx, [srcOffset]
                lea     esi, [eax+edx*4]
                mov     eax, [dst]
                mov     edx, [dstOffset]
                lea     edi, [eax+edx*4]
                cld
        @1:     lodsd
                and     eax, $00ffffff
                stosd
                loop    @1
                pop     edi
                pop     esi
    end;

    procedure sortStrings(const strings: UnicodeString_Array1d);
    var
        g: int;
        i: int;
        j: int;
        e: UnicodeString;
        f: UnicodeString;
    begin
        g := length(strings);
        for i := 0 to g - 2 do begin
            e := strings[i];
            for j := i + 1 to g - 1 do begin
                f := strings[j];
                if f < e then begin
                    strings[i] := f;
                    strings[j] := e;
                    e := f;
                end;
            end;
        end;
    end;

    function systemTimeToMalik(const systime: Windows.SYSTEMTIME): long;
    var
        mtime: DateTimeRecord absolute result;
    begin
        result := 0;
        mtime.year := systime.year;
        mtime.month := byte(systime.month);
        mtime.day := byte(systime.day);
        mtime.hour := byte(systime.hour);
        mtime.minute := byte(systime.minute);
        mtime.millisecond := Word(1000 * systime.second + systime.millisecond);
    end;

    function fileTimeToMalik(const filetime: Windows.FILETIME): long;
    var
        stime: Windows.SYSTEMTIME;
    begin
        initialize(stime);
        if Windows.fileTimeToSystemTime(@filetime, @stime) then begin
            result := systemTimeToMalik(stime);
            exit;
        end;
        result := 0;
    end;

    function malikTimeToFile(maliktime: long; filetime: Windows.PFILETIME): boolean;
    var
        mtime: DateTimeRecord absolute maliktime;
        stime: Windows.SYSTEMTIME;
        ms: int;
    begin
        ms := int(mtime.millisecond);
        stime.year := mtime.year;
        stime.month := Word(mtime.month);
        stime.day := Word(mtime.day);
        stime.hour := Word(mtime.hour);
        stime.minute := Word(mtime.minute);
        stime.second := Word(ms div 1000);
        stime.millisecond := Word(ms mod 1000);
        result := int(Windows.systemTimeToFileTime(@stime, filetime)) <> 0;
    end;
{%endregion}

{%region FontsNotFoundException }
    constructor FontsNotFoundException.create();
    begin
        inherited create();
    end;

    constructor FontsNotFoundException.create(const message: AnsiString);
    begin
        inherited create(message);
    end;
{%endregion}

{%region RecordingThread }
    function RecordingThread.isRowsEquals(rowIndex: int): boolean;
    var
        i: int;
        cwid: int;
        index: int;
        frame0: int_Array1d;
        frame1: int_Array1d;
    begin
        cwid := canvasWidth;
        index := rowIndex * cwid;
        frame0 := prevFramePixels;
        frame1 := currFramePixels;
        for i := 0 to cwid - 1 do begin
            if (frame0[index] and $00ffffff) <> (frame1[index] and $00ffffff) then begin
                result := false;
                exit;
            end;
            inc(index);
        end;
        result := true;
    end;

    function RecordingThread.isColsEquals(colIndex, rowOffset, rowLength: int): boolean;
    var
        i: int;
        cwid: int;
        index: int;
        frame0: int_Array1d;
        frame1: int_Array1d;
    begin
        cwid := canvasWidth;
        index := rowOffset * cwid + colIndex;
        frame0 := prevFramePixels;
        frame1 := currFramePixels;
        for i := 0 to rowLength - 1 do begin
            if (frame0[index] and $00ffffff) <> (frame1[index] and $00ffffff) then begin
                result := false;
                exit;
            end;
            inc(index, cwid);
        end;
        result := true;
    end;

    function RecordingThread.isNextFrameEquals(nextFramePixels: PIntArray; nextFrameScanline, nextFrameWidth, nextFrameHeight: int): boolean;
    var
        x: int;
        y: int;
        frame0Delta: int;
        frame0Index: int;
        frame0Pixels: PIntArray;
        frame1Delta: int;
        frame1Index: int;
        frame1Pixels: PIntArray;
    begin
        frame0Delta := canvasWidth - nextFrameWidth;
        frame0Index := 0;
        frame0Pixels := PIntArray(self.nextFramePixels);
        frame1Delta := nextFrameScanline - nextFrameWidth;
        frame1Index := 0;
        frame1Pixels := nextFramePixels;
        for y := 0 to nextFrameHeight - 1 do begin
            for x := 0 to nextFrameWidth - 1 do begin
                if (frame0Pixels[frame0Index] and $00ffffff) <> (frame1Pixels[frame1Index] and $00ffffff) then begin
                    result := false;
                    exit;
                end;
                inc(frame0Index);
                inc(frame1Index);
            end;
            inc(frame0Index, frame0Delta);
            inc(frame1Index, frame1Delta);
        end;
        result := true;
    end;

    function RecordingThread.toPNGData(left, top, width, height: int; opaque: boolean): int;
    var
        x: int;
        y: int;
        cwid: int;
        delta: int;
        index: int;
        pixel: int;
        frame0: int_Array1d;
        frame1: int_Array1d;
        filterStart: int;
        filterIndex: int;
        lineIndex: int;
        lineData: byte_Array1d;
    begin
        cwid := canvasWidth;
        delta := cwid - width;
        index := left + top * cwid;
        frame0 := prevFramePixels;
        frame1 := currFramePixels;
        lineIndex := 0;
        lineData := self.pngData;
        for y := 0 to height - 1 do begin
            lineData[lineIndex] := $01; { тип фильтрации - вычитание (1) }
            inc(lineIndex);
            filterStart := lineIndex + 4;
            for x := 0 to width - 1 do begin
                pixel := frame1[index] and $00ffffff;
                if opaque or (pixel <> (frame0[index] and $00ffffff)) then begin
                    lineData[lineIndex + 0] := byte(pixel shr 16); { R - уровень красного }
                    lineData[lineIndex + 1] := byte(pixel shr  8); { G - уровень зелёного }
                    lineData[lineIndex + 2] := byte(pixel);        { B - уровень синего }
                    lineData[lineIndex + 3] := byte($ff);          { A - уровень непрозрачного }
                end else begin
                    lineData[lineIndex + 0] := byte($00);
                    lineData[lineIndex + 1] := byte($00);
                    lineData[lineIndex + 2] := byte($00);
                    lineData[lineIndex + 3] := byte($00);
                end;
                inc(lineIndex, 4);
                inc(index);
            end;
            { применение типа фильтрации }
            for filterIndex := lineIndex - 1 downto filterStart do begin
                dec(lineData[filterIndex], lineData[filterIndex - 4]);
            end;
            inc(index, delta);
        end;
        result := lineIndex;
    end;

    constructor RecordingThread.create(const apngFileName: AnsiString; mainFramePixels: PIntArray; mainFrameScanline, mainFrameWidth, mainFrameHeight: int);
    var
        len: int;
        line: int;
        pixels: int_Array1d;
        eventName: AnsiString;
    begin
        inherited create(nil, '', false);
        len := mainFrameWidth * mainFrameHeight;
        pixels := int_Array1d_create(len);
        for line := 0 to mainFrameHeight - 1 do begin
            intArrayCopy(mainFramePixels, line * mainFrameScanline, PIntArray(pixels), line * mainFrameWidth, mainFrameWidth);
        end;
        eventName := 'Event.' + toDecString(int(Windows.getCurrentProcessId())) + '.' + toDecString(int(Windows.getCurrentThreadId())) + '.' + toDecString(getInternalCounterValue());
        self.canvasWidth := mainFrameWidth;
        self.canvasHeight := mainFrameHeight;
        self.nextFrameTime := Windows.getTickCount64();
        self.prevFramePixels := int_Array1d_create(len);
        self.currFramePixels := int_Array1d_create(len);
        self.nextFramePixels := pixels;
        self.pngData := byte_Array1d_create((4 * mainFrameWidth + 1) * mainFrameHeight);
        self.frameEvent := Windows.createEventA(nil, false, false, PChar(eventName));
        self.apngFileName := apngFileName;
    end;

    destructor RecordingThread.destroy;
    begin
        Windows.closeHandle(frameEvent);
        inherited destroy;
    end;

    procedure RecordingThread.run();
    var
        frameEvent: THandle;
        canvasWidth: int;
        canvasHeight: int;
        frameCounter: int;
        blockCounter: int;
        left: int;
        top: int;
        width: int;
        height: int;
        temp: int;
        actlPosition: long;
        crc: Checksum32;
        apngStream: FileStream;
        dataStream: DataOutput;
        apngChunk: ByteArrayOutputStream;
        dataChunk: DataOutput;
        pngData: byte_Array1d;
    begin
        try
            apngStream := FileStream.create(apngFileName);
            if apngStream.isInvalidHandle() then begin
                apngStream.free();
                exit;
            end;
            frameEvent := self.frameEvent;
            canvasWidth := self.canvasWidth;
            canvasHeight := self.canvasHeight;
            frameCounter := 0;
            blockCounter := 0;
            pngData := self.pngData;
            { PNG }
            crc := Crc32.create();
            dataStream := DataOutputStream.create(apngStream);
            dataStream.writeLong($89504e470d0a1a0a); { магическое число }
            { IHDR }
            apngChunk := ByteArrayOutputStream.create();
            dataChunk := DataOutputStream.create(apngChunk);
            dataChunk.writeInt($49484452);
            dataChunk.writeInt(canvasWidth);  { ширина холста }
            dataChunk.writeInt(canvasHeight); { высота холста }
            dataChunk.writeByte($08); { глубина цвета }
            dataChunk.writeByte($06); { тип пикселей - RGBA }
            dataChunk.writeByte($00); { алгоритм сжатия - только Deflate (0) }
            dataChunk.writeByte($00); { алгоритм фильтрации - адаптивная фильтрация с пятью типами (0) }
            dataChunk.writeByte($00); { алгоритм передачи данных - по умолчанию (0) }
            writePNGChunk(apngChunk, crc, dataStream);
            { tEXt }
            apngChunk := ByteArrayOutputStream.create();
            dataChunk := DataOutputStream.create(apngChunk);
            dataChunk.writeInt($74455874);
            dataChunk.writeFully(stringToByteArray('Software'#0'Malik Emulator https://malik-elaborarer.ru/emulator/'));
            writePNGChunk(apngChunk, crc, dataStream);
            { acTL }
            actlPosition := apngStream.position();
            apngChunk := ByteArrayOutputStream.create();
            dataChunk := DataOutputStream.create(apngChunk);
            dataChunk.writeInt($6163544c);
            dataChunk.writeInt(0); { количество кадров анимации - будет определено по окончании записи }
            dataChunk.writeInt(1); { количество повторов - 1 }
            writePNGChunk(apngChunk, crc, dataStream);
            repeat
                { ожидание следующего кадра }
                Windows.waitForSingleObject(frameEvent, Windows.INFINITE);
                try
                    { определение границ кадра }
                    if (frameCounter and $0f) = 0 then begin
                        left := 0;
                        top := 0;
                        width := canvasWidth;
                        height := canvasHeight;
                    end else begin
                        top := 0;
                        while (top < canvasHeight) and isRowsEquals(top) do inc(top);
                        temp := canvasHeight - 1;
                        while (temp >= 0) and isRowsEquals(temp) do dec(temp);
                        height := temp - top + 1;
                        left := 0;
                        while (left < canvasWidth) and isColsEquals(left, top, height) do inc(left);
                        temp := canvasWidth - 1;
                        while (temp >= 0) and isColsEquals(temp, top, height) do dec(temp);
                        width := temp - left + 1;
                    end;
                    { fcTL }
                    apngChunk := ByteArrayOutputStream.create();
                    dataChunk := DataOutputStream.create(apngChunk);
                    dataChunk.writeInt($6663544c);
                    dataChunk.writeInt(blockCounter); { порядковый номер куска анимации }
                    dataChunk.writeInt(width);        { ширина кадра }
                    dataChunk.writeInt(height);       { высота кадра }
                    dataChunk.writeInt(left);         { отступ слева кадра }
                    dataChunk.writeInt(top);          { отступ сверху кадра }
                    dataChunk.writeShort(int(nextFrameTime - currFrameTime)); { задержка кадра }
                    dataChunk.writeShort(1000);       { знаменатель задержки кадра (0.001 с) }
                    if (frameCounter and $0f) < $0f then begin
                        dataChunk.writeByte($00);     { APNG_DISPOSE_OP_NONE (0) }
                    end else begin
                        dataChunk.writeByte($01);     { APNG_DISPOSE_OP_BACKGROUND (1) }
                    end;
                    dataChunk.writeByte($01);         { APNG_BLEND_OP_OVER (1) }
                    writePNGChunk(apngChunk, crc, dataStream);
                    inc(blockCounter);
                    if frameCounter = 0 then begin
                        temp := toPNGData(left, top, width, height, true);
                        { IDAT }
                        apngChunk := ByteArrayOutputStream.create();
                        dataChunk := DataOutputStream.create(apngChunk);
                        dataChunk.writeInt($49444154);
                        dataChunk.writeFully(Zlib.compress(pngData, 0, temp, 5)); { сжатые данные }
                        writePNGChunk(apngChunk, crc, dataStream);
                    end else begin
                        temp := toPNGData(left, top, width, height, (frameCounter and $0f) = 0);
                        { faAT }
                        apngChunk := ByteArrayOutputStream.create();
                        dataChunk := DataOutputStream.create(apngChunk);
                        dataChunk.writeInt($66644154);
                        dataChunk.writeInt(blockCounter); { порядковый номер куска анимации }
                        dataChunk.writeFully(Zlib.compress(pngData, 0, temp, 5)); { сжатые данные }
                        writePNGChunk(apngChunk, crc, dataStream);
                        inc(blockCounter);
                    end;
                    inc(frameCounter);
                finally
                    isBusy := false;
                end;
            until isStop;
            { IEND }
            apngChunk := ByteArrayOutputStream.create();
            dataChunk := DataOutputStream.create(apngChunk);
            dataChunk.writeInt($49454e44);
            writePNGChunk(apngChunk, crc, dataStream);
            { окончание записи }
            apngStream.truncate();
            apngStream.seek(-apngStream.position() + actlPosition);
            { acTL - запись количества кадров анимации }
            apngChunk := ByteArrayOutputStream.create();
            dataChunk := DataOutputStream.create(apngChunk);
            dataChunk.writeInt($6163544c);
            dataChunk.writeInt(frameCounter); { количество кадров анимации }
            dataChunk.writeInt(1); { количество повторов - 1 }
            writePNGChunk(apngChunk, crc, dataStream);
        finally
            dataStream := nil;
            isClosed := true;
        end;
    end;

    procedure RecordingThread.addFrame(nextFramePixels: PIntArray; nextFrameScanline, nextFrameWidth, nextFrameHeight: int);
    var
        i: int;
        len: int;
        srcIndex: int;
        dstIndex: int;
        canvasWidth: int;
        canvasHeight: int;
        pausedTime: long;
        frame0: int_Array1d;
        frame1: int_Array1d;
        frame2: int_Array1d;
    begin
        if isPause then begin
            while isBusy do Windows.sleep(1);
            isPause := false;
            pausedTime := Windows.getTickCount64() - pauseTime;
            inc(currFrameTime, pausedTime);
            inc(nextFrameTime, pausedTime);
        end;
        if isBusy then exit;
        canvasWidth := self.canvasWidth;
        canvasHeight := self.canvasHeight;
        if nextFrameWidth > canvasWidth then nextFrameWidth := canvasWidth;
        if nextFrameHeight > canvasHeight then nextFrameHeight := canvasHeight;
        if isNextFrameEquals(nextFramePixels, nextFrameScanline, nextFrameWidth, nextFrameHeight) then exit;
        len := canvasWidth * canvasHeight;
        frame0 := self.prevFramePixels;
        frame1 := self.currFramePixels;
        frame2 := self.nextFramePixels;
        currFrameTime := nextFrameTime;
        nextFrameTime := Windows.getTickCount64();
        intArrayCopy(PIntArray(frame1), 0, PIntArray(frame0), 0, len);
        intArrayCopy(PIntArray(frame2), 0, PIntArray(frame1), 0, len);
        srcIndex := 0;
        dstIndex := 0;
        for i := 0 to nextFrameHeight - 1 do begin
            intArrayCopy(nextFramePixels, srcIndex, PIntArray(frame2), dstIndex, nextFrameWidth);
            inc(srcIndex, nextFrameScanline);
            inc(dstIndex, canvasWidth);
        end;
        isBusy := true;
        Windows.setEvent(frameEvent);
    end;

    procedure RecordingThread.pause();
    begin
        if isPause then exit;
        isPause := true;
        pauseTime := Windows.getTickCount64();
    end;

    procedure RecordingThread.stop();
    begin
        isStop := true;
        Windows.setEvent(frameEvent);
        while not isClosed do Windows.sleep(1);
    end;

    function RecordingThread.isPaused(): boolean;
    begin
        result := isPause;
    end;

    class procedure RecordingThread.writePNGChunk(pngChunk: ByteArrayOutputStream; crc: Checksum32; dataStream: DataOutput);
    var
        bytesChunk: byte_Array1d;
        lengthChunk: int;
    begin
        bytesChunk := pngChunk.toByteArray();
        lengthChunk := length(bytesChunk);
        crc.reset();
        crc.update(bytesChunk, 0, lengthChunk);
        dataStream.writeInt(lengthChunk - 4);
        dataStream.writeFully(bytesChunk);
        dataStream.writeInt(crc.getValue());
    end;
{%endregion}

{%region EmulationObject }
    constructor EmulationObject.create(TEmulationForm_self: TEmulationForm);
    begin
        inherited create();
        self.TEmulationForm_self := TEmulationForm_self;
    end;
{%endregion}

{%region EmulationRefCountObject }
    constructor EmulationRefCountObject.create(TEmulationForm_self: TEmulationForm);
    begin
        inherited create();
        self.TEmulationForm_self := TEmulationForm_self;
    end;
{%endregion}

{%region TEmulationForm }
    procedure TEmulationForm.formKeyPressed(sender: TObject; var key: Word; shift: TShiftState);
    var
        kstate: Windows.TKeyboardState;
    begin
        initialize(kstate);
        Windows.getKeyboardState(kstate);
        if ((key = VK_F4) or (key = VK_SPACE)) and (shift * [ssShift, ssCtrl, ssAlt] = [ssAlt]) then begin
            { Сочетания клавиш Alt+F4 и Alt+Пробел зарезервированы и не обрабатываются здесь. }
            exit;
        end;
        hostPressed := (kstate[$a5] and $80) <> 0;
        if hostPressed then begin
            { Нажата хост-клавиша (правый Alt). }
            if (key = VK_M) and (shift * [ssShift, ssCtrl, ssAlt] = [ssAlt]) then begin
                { Правый Alt+M – показать главное окно }
                owner.showMainWindow();
                key := 0;
                exit;
            end;
            if (key = VK_T) and (shift * [ssShift, ssCtrl, ssAlt] = [ssAlt]) then begin
                { Правый Alt+T – показать или скрыть панели инструментов }
                switchToolBarsVisible();
                key := 0;
                exit;
            end;
            if (key = VK_D) and (shift * [ssShift, ssCtrl, ssAlt] = [ssAlt]) then begin
                { Правый Alt+D – войти в режим отладки }
                showDisassemblerWindow();
                key := 0;
                exit;
            end;
            if (key = VK_R) and (shift * [ssShift, ssCtrl, ssAlt] = [ssAlt]) then begin
                { Правый Alt+R – начать или приостановить запись видео }
                startRecording();
                key := 0;
                exit;
            end;
            if (key = VK_S) and (shift * [ssShift, ssCtrl, ssAlt] = [ssAlt]) then begin
                { Правый Alt+S – остановить запись видео }
                stopRecording();
                key := 0;
                exit;
            end;
            if ((key = VK_OEM_PLUS) or (key = VK_ADD)) and (shift * [ssShift, ssCtrl, ssAlt] = [ssAlt]) then begin
                { Правый Alt+=, правый Alt+Num+ – увеличить изображение }
                zoomIn();
                key := 0;
                exit;
            end;
            if ((key = VK_OEM_MINUS) or (key = VK_SUBTRACT)) and (shift * [ssShift, ssCtrl, ssAlt] = [ssAlt]) then begin
                { Правый Alt+-, правый Alt+Num- – уменьшить изображение }
                zoomOut();
                key := 0;
                exit;
            end;
            if (key = VK_0) and (shift * [ssShift, ssCtrl, ssAlt] = [ssAlt]) then begin
                { Правый Alt+0 – повернуть изображение вправо }
                rotateRight();
                key := 0;
                exit;
            end;
            if (key = VK_9) and (shift * [ssShift, ssCtrl, ssAlt] = [ssAlt]) then begin
                { Правый Alt+9 – повернуть изображение влево }
                rotateLeft();
                key := 0;
                exit;
            end;
            if (key = VK_8) and (shift * [ssShift, ssCtrl, ssAlt] = [ssAlt]) then begin
                { Правый Alt+8 – подогнать размер окна под изображение }
                adjustMonitorSize();
                key := 0;
                exit;
            end;
            if (key = VK_RETURN) and (shift * [ssShift, ssCtrl, ssAlt] = [ssAlt]) then begin
                { Правый Alt+Enter – переключение между оконным и полноэкранным режимом }
                switchFullScreenMode();
                key := 0;
                exit;
            end;
            exit;
        end;
        if (key >= $00) and (key <= $ff) and (key in [8..9, 13, 16..20, 27, 32..40, 45..46, 48..57, 65..90, 93, 96..135, 144..145, 160..165, 186..192, 219..222]) then begin
            { Если не нажата хост-клавиша, то передаём нажатую клавишу в эмулируемую программу. }
            keyPressed := int(key);
            if key in [8..9, 13, 16..20, 27, 33..40, 45..46, 93, 112..135, 144..145, 160..165] then begin
                processor.interrupt($10, buildLong(keyPressed, 0));
                key := 0;
            end;
        end;
    end;

    procedure TEmulationForm.formKeyReleased(sender: TObject; var key: Word; shift: TShiftState);
    begin
        if (key = VK_ESCAPE) and (shift * [ssShift, ssCtrl, ssAlt] = []) and processor.isTerminated() then begin
            close();
            exit;
        end;
        if not hostPressed then begin
            { Если не нажата хост-клавиша, то передаём нажатую клавишу в эмулируемую программу. }
            processor.interrupt($10, buildLong(int(key), MIN_INT));
        end;
    end;

    procedure TEmulationForm.formUTF8KeyPressed(sender: TObject; var utf8Key: TUTF8Char);
    var
        charCode: int;
    begin
        if hostPressed then begin
            utf8Key := '';
            exit;
        end;
        charCode := getCharCodes(AnsiString(utf8Key))[0] and $00ffffff;
        if (charCode >= 32) then begin
            { Если не нажата хост-клавиша, то передаём нажатую клавишу в эмулируемую программу. }
            processor.interrupt($10, buildLong(keyPressed, charCode));
        end;
        utf8Key := '';
    end;

    procedure TEmulationForm.formPointerPressed(sender: TObject; button: TMouseButton; shift: TShiftState; x, y: integer);
    var
        p: TPoint;
        b: int;
    begin
        p.x := 0;
        p.y := 0;
        if getVirtualCoordinates(x, y, @p) then begin
            case button of
            mbLeft: begin
                b := $00;
            end;
            mbRight: begin
                b := $01;
            end;
            mbMiddle: begin
                b := $02;
            end;
            else
                exit;
            end;
            processor.interrupt($11, zeroExtend(p.x and $ffff) + zeroExtend((p.y and $ffff) shl 16) + (long(b) shl 32));
        end;
    end;

    procedure TEmulationForm.formPointerDragged(sender: TObject; shift: TShiftState; x, y: integer);
    var
        p: TPoint;
    begin
        if shift * [ssLeft, ssRight, ssMiddle] = [] then begin
            exit;
        end;
        p.x := 0;
        p.y := 0;
        if getVirtualCoordinates(x, y, @p) then begin
            processor.interruptIfAllow($11, zeroExtend(p.x and $ffff) + zeroExtend((p.y and $ffff) shl 16) + (long($0f) shl 32));
        end;
    end;

    procedure TEmulationForm.formPointerReleased(sender: TObject; button: TMouseButton; shift: TShiftState; x, y: integer);
    var
        p: TPoint;
        b: int;
    begin
        p.x := 0;
        p.y := 0;
        if getVirtualCoordinates(x, y, @p) then begin
            case button of
            mbLeft: begin
                b := $08;
            end;
            mbRight: begin
                b := $09;
            end;
            mbMiddle: begin
                b := $0a;
            end;
            else
                exit;
            end;
            processor.interrupt($11, zeroExtend(p.x and $ffff) + zeroExtend((p.y and $ffff) shl 16) + (long(b) shl 32));
        end;
    end;

    procedure TEmulationForm.formScrollingWheelDown(sender: TObject; shift: TShiftState; pointerCoordinates: TPoint; var handled: boolean);
    var
        x: int absolute pointerCoordinates.x;
        y: int absolute pointerCoordinates.y;
        p: TPoint;
    begin
        p.x := 0;
        p.y := 0;
        if getVirtualCoordinates(x, y, @p) then begin
            processor.interrupt($11, zeroExtend(p.x and $ffff) + zeroExtend((p.y and $ffff) shl 16) + (long($05) shl 32));
            processor.interrupt($11, zeroExtend(p.x and $ffff) + zeroExtend((p.y and $ffff) shl 16) + (long($0d) shl 32));
        end;
    end;

    procedure TEmulationForm.formScrollingWheelUp(sender: TObject; shift: TShiftState; pointerCoordinates: TPoint; var handled: boolean);
    var
        x: int absolute pointerCoordinates.x;
        y: int absolute pointerCoordinates.y;
        p: TPoint;
    begin
        p.x := 0;
        p.y := 0;
        if getVirtualCoordinates(x, y, @p) then begin
            processor.interrupt($11, zeroExtend(p.x and $ffff) + zeroExtend((p.y and $ffff) shl 16) + (long($04) shl 32));
            processor.interrupt($11, zeroExtend(p.x and $ffff) + zeroExtend((p.y and $ffff) shl 16) + (long($0c) shl 32));
        end;
    end;

    procedure TEmulationForm.formWindowStateChange(sender: TObject);
    var
        currWindowState: TWindowState;
        prevWindowState: TWindowState;
    begin
        currWindowState := self.windowState;
        prevWindowState := self.prevWindowState;
        if (currWindowState = wsMinimized) and (prevWindowState <> wsMinimized) then begin
            processor.interrupt($12, long(1) shl 32);
        end else
        if (currWindowState <> wsMinimized) and (prevWindowState = wsMinimized) then begin
            processor.interrupt($12, long(0) shl 32);
        end;
        self.prevWindowState := currWindowState;
    end;

    procedure TEmulationForm.formResize(sender: TObject);
    var
        w: int;
        h: int;
    begin
        recalculateStatusPanelsWidth();
        if (processor <> nil) and (not modeChanging) and (not isFullScreenMode()) then begin
            w := getMonitorWidth();
            h := getMonitorHeight();
            processor.interruptIfAllow($13, buildLong(0, zeroExtend(w and $ffff) + zeroExtend((h and $ffff) shl 16)));
        end;
    end;

    procedure TEmulationForm.formPaint(sender: TObject);
    var
        w: int;
        h: int;
        img: TBitmap;
        scl: TBitmap;
    begin
        w := getOutputAreaWidth();
        h := getOutputAreaHeight();
        img := outputImage;
        if img = nil then begin
            exit;
        end;
        scl := scaledImageBitmap;
        if (img.width <> w) or (img.height <> h) then begin
            img.setSize(w, h);
        end;
        screenLeft := sar(img.width - scl.width, 1);
        screenTop := sar(img.height - scl.height, 1);
        with img.canvas do begin
            brush.style := bsSolid;
            brush.color := $000000;
            fillRect(bounds(0, 0, img.width, img.height));
            draw(screenLeft, screenTop, scl);
        end;
        self.canvas.draw(getOutputAreaLeft(), getOutputAreaTop(), img);
    end;

    procedure TEmulationForm.formClose(sender: TObject; var action: TCloseAction);
    begin
        stopRecording();
        if terminated then begin
            action := caFree;
            exit;
        end;
        action := caNone;
        closeOnTerminate := true;
        if nowDebugging then begin
            programmeResume();
        end;
        processor.terminate();
    end;

    procedure TEmulationForm.fpsTimer(sender: TObject);
    begin
        showFramesPerSecondStatus(frames);
        frames := 0;
    end;

    procedure TEmulationForm.buttonClick(sender: TObject);
    begin
        if sender = sbtnViewRotateLeft then begin
            rotateLeft();
            exit;
        end;
        if sender = sbtnViewRotateRight then begin
            rotateRight();
            exit;
        end;
        if sender = sbtnViewZoomIn then begin
            zoomIn();
            exit;
        end;
        if sender = sbtnViewZoomOut then begin
            zoomOut();
            exit;
        end;
        if sender = sbtnMonitorAdjustSize then begin
            adjustMonitorSize();
            exit;
        end;
        if sender = sbtnMonitorFullScreen then begin
            switchFullScreenMode();
            exit;
        end;
        if sender = sbtnMonitorRecordStart then begin
            startRecording();
            exit;
        end;
        if sender = sbtnMonitorRecordStop then begin
            stopRecording();
            exit;
        end;
    end;

    procedure TEmulationForm.windowActivate(active: boolean);
    begin
        if isFullScreenMode() then begin
            if active then begin
                windowState := wsMaximized;
            end else begin
                windowState := wsMinimized;
            end;
            formWindowStateChange(self);
        end;
    end;

    procedure TEmulationForm.processorBegin();
    begin
        showExecutionStatus('Выполняется');
        sbtnMonitorFullScreen.enabled := true;
        if owner.getSetting(SECTION_EMULATOR, KEY_AUTO_FULLSCREEN, '0') = '1' then begin
            setFullScreenMode(true);
        end;
    end;

    procedure TEmulationForm.processorEnd();
    var
        d: TDisassemblerForm;
    begin
        setFullScreenMode(false);
        showExecutionStatus('Завершён');
        sbtnMonitorFullScreen.enabled := false;
        d := disassembler;
        d.caption := d.caption + ' [ПРОГРАММА ЗАВЕРШЕНА]';
        terminated := true;
        if closeOnTerminate or (owner.getSetting(SECTION_EMULATOR, KEY_AUTO_CLOSEWND, '0') = '1') then begin
            close();
        end;
    end;

    procedure TEmulationForm.debugBegin();
    var
        d: TDisassemblerForm;
    begin
        showExecutionStatus('Отладка');
        d := disassembler;
        d.showOnTop();
        d.setFocus();
        d.showContents(contextID);
    end;

    procedure TEmulationForm.debugEnd();
    begin
        showExecutionStatus('Выполняется');
    end;

    procedure TEmulationForm.recalculateStatusPanelsWidth();
    begin
        status.panels.items[0].width := clientWidth - 160;
    end;

    procedure TEmulationForm.screenUpdate(fromProgramme: boolean);
    var
        x: int;
        y: int;
        z: int;
        w: int;
        h: int;
        srcScanline: int;
        dstScanline: int;
        srcPixels: PIntArray;
        dstPixels: PIntArray;
        recording: RecordingThread;
    begin
        { проверка максимума кадров в секунду }
        if fromProgramme and (frames >= maximumFrames) then begin
            exit;
        end;
        renderInProgress := true;
        try
            srcPixels := sourceImagePixels;
            dstPixels := rotatedImagePixels;
            srcScanline := sourceImageScanline;
            dstScanline := rotatedImageScanline;
            { выполняем запись видео, если нужно }
            recording := self.recording;
            if (recording <> nil) and not recording.isPaused() then begin
                recording.addFrame(srcPixels, srcScanline, sourceImageWidth, sourceImageHeight);
            end;
            { выполняем поворот изображения }
            case screenRotate and 3 of
             0: begin { без поворота }
                w := sourceImageWidth;
                h := sourceImageHeight;
                for y := 0 to h - 1 do begin
                    intArrayCopy(srcPixels, y * srcScanline, dstPixels, y * dstScanline, w);
                end;
            end;
             1: begin { поворот на 90° вправо }
                w := sourceImageHeight;
                h := sourceImageWidth;
                for y := 0 to h - 1 do for x := 0 to w - 1 do begin
                    dstPixels[x + y * dstScanline] := srcPixels[y + (w - x - 1) * srcScanline];
                end;
            end;
             2: begin { поворот на 180° }
                w := sourceImageWidth;
                h := sourceImageHeight;
                for y := 0 to h - 1 do for x := 0 to w - 1 do begin
                    dstPixels[x + y * dstScanline] := srcPixels[(w - x - 1) + (h - y - 1) * srcScanline];
                end;
            end;
             3: begin { поворот на 90° влево }
                w := sourceImageHeight;
                h := sourceImageWidth;
                for y := 0 to h - 1 do for x := 0 to w - 1 do begin
                    dstPixels[x + y * dstScanline] := srcPixels[(h - y - 1) + x * srcScanline];
                end;
            end;
            else
                exit;
            end;
        finally
            renderInProgress := false;
        end;
        { выполняем масштабирование изображения }
        z := screenZoom;
        if z = 1 then begin
            setScaledImageSize(w, h);
            scaledImageBitmap.canvas.copyRect(bounds(0, 0, w, h), rotatedImageBitmap.canvas, bounds(0, 0, w, h));
        end else begin
            setScaledImageSize(z * w, z * h);
            if scalingAlgorithm <> nil then begin
                scalingAlgorithm.setSource(dstPixels, w, h, srcScanline);
                case z of
                 2: begin
                    scalingAlgorithm.scale2x(scaledImagePixels, 2 * w, true);
                end;
                 3: begin
                    scalingAlgorithm.scale3x(scaledImagePixels, 3 * w, true);
                end;
                 4: begin
                    scalingAlgorithm.scale4x(scaledImagePixels, 4 * w, true);
                end;
                end;
            end else begin
                scaledImageBitmap.canvas.copyRect(bounds(0, 0, z * w, z * h), rotatedImageBitmap.canvas, bounds(0, 0, w, h));
            end;
        end;
        { выводим изображение на экран }
        if resolutionChanged then begin
            resolutionChanged := false;
            setMonitorSize(screenImageWidth, screenImageHeight);
        end;
        if fromProgramme then begin
            inc(frames);
        end;
        formPaint(self);
    end;

    function TEmulationForm.playerPCMCreate(signalParameters, blockLength: int): int;
    var
        samplesPerSecond: int;
        bitsPerSample: int;
        channelsCount: int;
        soundPlayer: PlayerPCM;
        emu: EmulationOfPlayer;
    begin
        samplesPerSecond := signalParameters and $ffff;
        bitsPerSample := (signalParameters shr 16) and $ff;
        channelsCount := ((signalParameters shr 24) and $ff) + 1;
        soundPlayer := PlayerPCM.create(samplesPerSecond, bitsPerSample, channelsCount, blockLength, self);
        if soundPlayer.isInvalidData() or soundPlayer.isNoDevices() then begin
            soundPlayer.free();
            result := 0;
            exit;
        end;
        emu := EmulationOfPlayerPCM.create(self, soundPlayer);
        emu.handle := addSystemObject(emu);
        result := emu.handle;
    end;

    function TEmulationForm.playerPCMLoadBlock(playerHandle, descriptorAddress: int): int;
    var
        soundPlayer: PlayerPCM;
        descriptor: PMalikDataDescriptor;
        p: Pointer;
        length: int;
        data: long_Array1d;
    begin
        soundPlayer := getPlayerPCM(playerHandle);
        if soundPlayer = nil then begin
            result := 0;
            exit;
        end;
        descriptor := PMalikDataDescriptor(processor.getMemory(descriptorAddress, sizeof(MalikDataDescriptor)));
        if descriptor = nil then begin
            result := 0;
            exit;
        end;
        length := descriptor.count;
        if (length <= 0) or (length > PlayerPCM.MAX_BLOCK_LENGTH) then begin
            result := 0;
            exit;
        end;
        p := processor.getMemory(descriptor.address, length shl 3);
        if p = nil then begin
            result := 0;
            exit;
        end;
        data := self.data;
        if (data = nil) or (System.length(data) < PlayerPCM.MAX_BLOCK_LENGTH) then begin
            data := long_Array1d_create(PlayerPCM.MAX_BLOCK_LENGTH);
            self.data := data;
        end;
        System.move(p^, data[0], length shl 3);
        result := soundPlayer.loadBlock(data, 0, length);
    end;

    function TEmulationForm.playerPCMPlayback(playerHandle, command: int): int;
    var
        soundPlayer: PlayerPCM;
    begin
        soundPlayer := getPlayerPCM(playerHandle);
        if soundPlayer = nil then begin
            result := -1;
            exit;
        end;
        case command of
         0: begin
            result := soundPlayer.reset();
        end;
         1: begin
            result := soundPlayer.start();
        end;
         2: begin
            result := soundPlayer.stop();
        end;
        else
            result := -1;
        end;
    end;

    function TEmulationForm.playerPCMGetState(playerHandle: int): int;
    var
        soundPlayer: PlayerPCM;
    begin
        soundPlayer := getPlayerPCM(playerHandle);
        if soundPlayer = nil then begin
            result := -1;
            exit;
        end;
        result := soundPlayer.getState();
    end;

    function TEmulationForm.playerPCMSetPosition(playerHandle, position: int): int;
    var
        soundPlayer: PlayerPCM;
    begin
        soundPlayer := getPlayerPCM(playerHandle);
        if soundPlayer = nil then begin
            result := -1;
            exit;
        end;
        result := soundPlayer.setCurrentBlockPosition(position);
    end;

    function TEmulationForm.playerPCMGetPosition(playerHandle: int): long;
    var
        soundPlayer: PlayerPCM;
    begin
        soundPlayer := getPlayerPCM(playerHandle);
        if soundPlayer = nil then begin
            result := -1;
            exit;
        end;
        result := buildLong(soundPlayer.getCurrentBlockPosition(), soundPlayer.getCurrentBlockIndex());
    end;

    function TEmulationForm.playerPCMVolume(playerHandle, volume: int): int;
    var
        soundPlayer: PlayerPCM;
    begin
        soundPlayer := getPlayerPCM(playerHandle);
        if soundPlayer = nil then begin
            result := -1;
            exit;
        end;
        if volume = -1 then begin
            result := soundPlayer.getVolume();
            exit;
        end;
        result := soundPlayer.setVolume(volume and $7f);
    end;

    function TEmulationForm.playerPCMDestroy(playerHandle: int): int;
    begin
        result := 0;
        closeSystemObject(playerHandle, EmulationOfPlayerPCM);
    end;

    function TEmulationForm.playerMIDICreate(): int;
    var
        soundPlayer: PlayerMIDI;
        emu: EmulationOfPlayer;
    begin
        soundPlayer := PlayerMIDI.create(self);
        if soundPlayer.isInvalidData() or soundPlayer.isNoDevices() then begin
            soundPlayer.free();
            result := 0;
            exit;
        end;
        emu := EmulationOfPlayerMIDI.create(self, soundPlayer);
        emu.handle := addSystemObject(emu);
        result := emu.handle;
    end;

    function TEmulationForm.playerMIDILoadBlock(playerHandle, descriptorAddress: int): int;
    var
        soundPlayer: PlayerMIDI;
        descriptor: PMalikDataDescriptor;
        p: Pointer;
        length: int;
        data: long_Array1d;
    begin
        soundPlayer := getPlayerMIDI(playerHandle);
        if soundPlayer = nil then begin
            result := 0;
            exit;
        end;
        descriptor := PMalikDataDescriptor(processor.getMemory(descriptorAddress, sizeof(MalikDataDescriptor)));
        if descriptor = nil then begin
            result := 0;
            exit;
        end;
        length := descriptor.count;
        if length = 0 then begin
            soundPlayer.sendMessage(descriptor.address);
            result := 1;
            exit;
        end;
        if length < 0 then begin
            result := 0;
            exit;
        end;
        p := processor.getMemory(descriptor.address, length shl 3);
        if p = nil then begin
            result := 0;
            exit;
        end;
        data := self.data;
        if (data = nil) or (System.length(data) < length) then begin
            data := long_Array1d_create(length);
            self.data := data;
        end;
        System.move(p^, data[0], length shl 3);
        result := soundPlayer.loadBlock(data, 0, length);
    end;

    function TEmulationForm.playerMIDIPlayback(playerHandle, command: int): int;
    var
        soundPlayer: PlayerMIDI;
    begin
        soundPlayer := getPlayerMIDI(playerHandle);
        if soundPlayer = nil then begin
            result := -1;
            exit;
        end;
        case command of
         0: begin
            result := soundPlayer.reset();
        end;
         1: begin
            result := soundPlayer.start();
        end;
         2: begin
            result := soundPlayer.stop();
        end;
        else
            result := -1;
        end;
    end;

    function TEmulationForm.playerMIDIGetState(playerHandle: int): int;
    var
        soundPlayer: PlayerMIDI;
    begin
        soundPlayer := getPlayerMIDI(playerHandle);
        if soundPlayer = nil then begin
            result := -1;
            exit;
        end;
        result := soundPlayer.getState();
    end;

    function TEmulationForm.playerMIDISetPosition(playerHandle, position: int): int;
    var
        soundPlayer: PlayerMIDI;
    begin
        soundPlayer := getPlayerMIDI(playerHandle);
        if soundPlayer = nil then begin
            result := -1;
            exit;
        end;
        result := soundPlayer.setCurrentBlockPosition(position);
    end;

    function TEmulationForm.playerMIDIGetPosition(playerHandle: int): long;
    var
        soundPlayer: PlayerMIDI;
    begin
        soundPlayer := getPlayerMIDI(playerHandle);
        if soundPlayer = nil then begin
            result := -1;
            exit;
        end;
        result := buildLong(soundPlayer.getCurrentBlockPosition(), soundPlayer.getCurrentBlockIndex());
    end;

    function TEmulationForm.playerMIDIVolume(playerHandle, volume: int): int;
    var
        soundPlayer: PlayerMIDI;
    begin
        soundPlayer := getPlayerMIDI(playerHandle);
        if soundPlayer = nil then begin
            result := -1;
            exit;
        end;
        if volume = -1 then begin
            result := soundPlayer.getVolume();
            exit;
        end;
        result := soundPlayer.setVolume(volume and $7f);
    end;

    function TEmulationForm.playerMIDIDestroy(playerHandle: int): int;
    begin
        result := 0;
        closeSystemObject(playerHandle, EmulationOfPlayerMIDI);
    end;

    function TEmulationForm.getPlayerPCM(playerHandle: int): PlayerPCM;
    var
        emu: SystemObject;
        obj: TObject;
    begin
        emu := getSystemObject(playerHandle);
        if emu = nil then begin
            result := nil;
            exit;
        end;
        obj := emu.asObject();
        if not (obj is EmulationOfPlayer) then begin
            result := nil;
            exit;
        end;
        obj := EmulationOfPlayer(obj).soundPlayer;
        if not (obj is PlayerPCM) then begin
            result := nil;
            exit;
        end;
        result := PlayerPCM(obj);
    end;

    function TEmulationForm.getPlayerMIDI(playerHandle: int): PlayerMIDI;
    var
        emu: SystemObject;
        obj: TObject;
    begin
        emu := getSystemObject(playerHandle);
        if emu = nil then begin
            result := nil;
            exit;
        end;
        obj := emu.asObject();
        if not (obj is EmulationOfPlayer) then begin
            result := nil;
            exit;
        end;
        obj := EmulationOfPlayer(obj).soundPlayer;
        if not (obj is PlayerMIDI) then begin
            result := nil;
            exit;
        end;
        result := PlayerMIDI(obj);
    end;

    function TEmulationForm.readNullTerminatedUnicodeString(address: int): UnicodeString;
    const
        MAX_LENGTH = int(1024);
    var
        i: int;
        l: int;
        s: PWideChar;
    begin
        l := MAX_LENGTH;
        repeat
            s := PWideChar(processor.getMemory(address, l * sizeof(wchar)));
            if s <> nil then begin
                break;
            end;
            if l <= 1 then begin
                result := '';
                exit;
            end;
            l := sar(l, 1);
        until false;
        for i := 0 to l - 1 do begin
            if s[i] = #$0000 then begin
                l := i;
                break;
            end;
        end;
        result := UnicodeString_create(l);
        move(s[0], result[1], l * sizeof(wchar));
    end;

    procedure TEmulationForm.createSystemMenu();
    var
        flags: int;
        sysmenu: HMenu;
    begin
        flags := MF_STRING;
        if panelView.visible then begin
            flags := flags or MF_CHECKED;
        end;
        sysmenu := Windows.getSystemMenu(handle, false);
        Windows.appendMenuW(sysmenu, MF_SEPARATOR, 0, '');
        Windows.appendMenuW(sysmenu, flags, ID_TOOLBARS, PWideChar(STR_TOOLBARS));
    end;

    procedure TEmulationForm.startRecording();
    var
        number: int;
        canvasWidth: int;
        canvasHeight: int;
        recording: RecordingThread;
        apngFileName: AnsiString;
        probeFileName: AnsiString;
    begin
        canvasWidth := sourceImageWidth;
        canvasHeight := sourceImageHeight;
        recording := self.recording;
        if (recording = nil) and (canvasWidth > 0) and (canvasHeight > 0) then begin
            apngFileName := getEmulatorDirectory() + DIRECTORY_VIDEO;
            if not fileExists(apngFileName) then begin
                createDirectory(apngFileName);
            end;
            apngFileName := apngFileName + DIRECTORY_SEPARATOR + caption + ' ';
            for number := 1 to MAX_INT do begin
                probeFileName := apngFileName + '(' + toDecString(number) + ').png';
                if not fileExists(probeFileName) then begin
                    apngFileName := probeFileName;
                    break;
                end;
            end;
            recording := RecordingThread.create(apngFileName, sourceImagePixels, sourceImageScanline, canvasWidth, canvasHeight);
            recording.start();
            self.recording := recording;
            showRecordingStatus('ЗАП');
            sbtnMonitorRecordStop.enabled := true;
        end else
        if recording <> nil then begin
            if recording.isPaused() then begin
                recording.addFrame(sourceImagePixels, sourceImageScanline, canvasWidth, canvasHeight);
                showRecordingStatus('ЗАП');
            end else begin
                recording.pause();
                showRecordingStatus('пауза');
            end;
        end;
    end;

    procedure TEmulationForm.stopRecording();
    var
        canvasWidth: int;
        canvasHeight: int;
        blackPixels: int_Array1d;
        recording: RecordingThread;
    begin
        recording := self.recording;
        if recording <> nil then begin
            canvasWidth := sourceImageWidth;
            canvasHeight := sourceImageHeight;
            blackPixels := int_Array1d_create(canvasWidth * canvasHeight);
            recording.addFrame(PIntArray(blackPixels), canvasWidth, canvasWidth, canvasHeight);
            recording.stop();
            recording.free();
            self.recording := nil;
            showRecordingStatus('');
            sbtnMonitorRecordStop.enabled := false;
        end;
    end;

    procedure TEmulationForm.zoomIn();
    begin
        if screenZoom < 4 then begin
            inc(screenZoom);
            showScreenZoomStatus(screenZoom);
            sbtnViewZoomIn.enabled := screenZoom < 4;
            sbtnViewZoomOut.enabled := true;
            resolutionChanged := true;
            screenUpdate(false);
        end;
    end;

    procedure TEmulationForm.zoomOut();
    begin
        if screenZoom > 1 then begin
            dec(screenZoom);
            showScreenZoomStatus(screenZoom);
            sbtnViewZoomIn.enabled := true;
            sbtnViewZoomOut.enabled := screenZoom > 1;
            resolutionChanged := true;
            screenUpdate(false);
        end;
    end;

    procedure TEmulationForm.rotateLeft();
    begin
        screenRotate := (screenRotate - 1) and $03;
        resolutionChanged := true;
        screenUpdate(false);
    end;

    procedure TEmulationForm.rotateRight();
    begin
        screenRotate := (screenRotate + 1) and $03;
        resolutionChanged := true;
        screenUpdate(false);
    end;

    procedure TEmulationForm.adjustMonitorSize();
    begin
        setMonitorSize(screenImageWidth, screenImageHeight);
        formPaint(self);
    end;

    procedure TEmulationForm.switchFullScreenMode();
    begin
        if processor.isExecuting() then begin
            setFullScreenMode(borderStyle <> bsNone);
        end;
    end;

    procedure TEmulationForm.switchToolBarsVisible();
    var
        flags: int;
        visible: boolean;
        control: TWinControl;
    begin
        control := panelView;
        visible := not control.visible;
        flags := MF_BYCOMMAND or MF_STRING;
        if visible then begin
            flags := flags or MF_CHECKED;
        end;
        beginFormUpdate();
        try
            control.visible := visible;
            panelMonitor.visible := visible;
            Windows.modifyMenuW(Windows.getSystemMenu(handle, false), ID_TOOLBARS, flags, ID_TOOLBARS, PWideChar(STR_TOOLBARS));
        finally
            endFormUpdate();
        end;
    end;

    procedure TEmulationForm.setScaledImageSize(width, height: int);
    var
        b: TBitmap;
    begin
        b := scaledImageBitmap;
        if b.empty or (b.width <> width) or (b.height <> height) then begin
            scaledImagePixels := createPixelImage(b, width, height);
        end;
    end;

    procedure TEmulationForm.setFullScreenMode(fullScreen: boolean);
    var
        w: int;
        h: int;
        monitor: TMonitor;
    begin
        if fullScreen = isFullScreenMode() then begin
            exit;
        end;
        modeChanging := true;
        try
            if fullScreen then begin
                if (screenRotate and $01) = 0 then begin
                    w := screenImageWidth;
                    h := screenImageHeight;
                end else begin
                    w := screenImageHeight;
                    h := screenImageWidth;
                end;
                normalScreenZoom := screenZoom;
                if (w <> 0) and (h <> 0) then begin
                    monitor := self.monitor;
                    screenZoom := min(4, max(1, min(monitor.width div w, monitor.height div h)));
                end;
                borderStyle := bsNone;
                windowState := wsMaximized;
                status.visible := false;
                sbtnMonitorAdjustSize.enabled := false;
            end else begin
                sbtnMonitorAdjustSize.enabled := true;
                status.visible := true;
                windowState := wsNormal;
                borderStyle := bsSizeable;
                screenZoom := normalScreenZoom;
            end;
            showScreenZoomStatus(screenZoom);
            createSystemMenu();
            screenUpdate(false);
        finally
            windowHandle := handle;
            modeChanging := false;
        end;
    end;

    procedure TEmulationForm.setMonitorSize(width, height: int);
    var
        screenZoom: int;
        oldWidth: int;
        oldHeight: int;
        newWidth: int;
        newHeight: int;
    begin
        if isFullScreenMode() then begin
            exit;
        end;
        screenZoom := self.screenZoom;
        oldWidth := getOutputAreaWidth();
        oldHeight := getOutputAreaHeight();
        if (screenRotate and 1) = 0 then begin
            newWidth := width * screenZoom;
            newHeight := height * screenZoom;
        end else begin
            newWidth := height * screenZoom;
            newHeight := width * screenZoom;
        end;
        setOutputAreaBounds(left + sar(oldWidth - newWidth, 1), top + sar(oldHeight - newHeight, 1), newWidth, newHeight);
    end;

    procedure TEmulationForm.setOutputAreaBounds(left, top, width, height: int);
    var
        control: TWinControl;
    begin
        if isFullScreenMode() then begin
            exit;
        end;
        control := panelView;
        if control.visible then begin
            inc(width, control.width);
        end;
        control := panelMonitor;
        if control.visible then begin
            inc(width, control.width);
        end;
        control := status;
        if control.visible then begin
            inc(height, control.height);
        end;
        onResize := nil;
        onPaint := nil;
        try
            setBounds(left, top, width, height);
        finally
            onResize := formResize;
            onPaint := formPaint;
            recalculateStatusPanelsWidth();
        end;
    end;

    function TEmulationForm.isOutputGraphicInvalid(): boolean;
    begin
        result := (outputGraphicPixels = nil) or (abs(outputGraphicScanline) < outputGraphicWidth) or (outputGraphicWidth <= 0) or (outputGraphicHeight <= 0);
    end;

    function TEmulationForm.isFullScreenMode(): boolean;
    begin
        result := (not modeChanging) and (borderStyle = bsNone);
    end;

    function TEmulationForm.getMonitorWidth(): int;
    var
        size: int;
    begin
        if (screenRotate and 1) = 0 then begin
            size := getOutputAreaWidth();
        end else begin
            size := getOutputAreaHeight();
        end;
        result := max(MIN_SCREEN_WIDTH, min(MAX_SCREEN_WIDTH, size div screenZoom));
    end;

    function TEmulationForm.getMonitorHeight(): int;
    var
        size: int;
    begin
        if (screenRotate and 1) <> 0 then begin
            size := getOutputAreaWidth();
        end else begin
            size := getOutputAreaHeight();
        end;
        result := max(MIN_SCREEN_HEIGHT, min(MAX_SCREEN_HEIGHT, size div screenZoom));
    end;

    function TEmulationForm.getOutputAreaLeft(): int;
    var
        control: TWinControl;
    begin
        control := panelView;
        if control.visible then begin
            result := control.width;
            exit;
        end;
        result := 0;
    end;

    function TEmulationForm.getOutputAreaTop(): int;
    begin
        result := 0;
    end;

    function TEmulationForm.getOutputAreaWidth(): int;
    var
        control: TWinControl;
    begin
        result := clientWidth;
        control := panelView;
        if control.visible then begin
            dec(result, control.width);
        end;
        control := panelMonitor;
        if control.visible then begin
            dec(result, control.width);
        end;
    end;

    function TEmulationForm.getOutputAreaHeight(): int;
    var
        control: TWinControl;
    begin
        result := clientHeight;
        control := status;
        if control.visible then begin
            dec(result, control.height);
        end;
    end;

    function TEmulationForm.getVirtualCoordinates(x, y: int; virtualCoordinates: PPoint): boolean;
    var
        r: int;
        z: int;
        l: int;
        t: int;
        w: int;
        h: int;
    begin
        dec(x, getOutputAreaLeft());
        dec(y, getOutputAreaTop());
        r := screenRotate;
        z := screenZoom;
        l := screenLeft;
        t := screenTop;
        if (r and $01) = 0 then begin
            w := z * sourceImageWidth;
            h := z * sourceImageHeight;
        end else begin
            w := z * sourceImageHeight;
            h := z * sourceImageWidth;
        end;
        if (x < l) or (x >= l + w) or (y < t) or (y >= t + h) then begin
            result := false;
            exit;
        end;
        case r and $03 of
         0: begin
            virtualCoordinates.x := (x - l) div z;
            virtualCoordinates.y := (y - t) div z;
        end;
         1: begin
            virtualCoordinates.x := (y - t) div z;
            virtualCoordinates.y := (w - x + l) div z;
        end;
         2: begin
            virtualCoordinates.x := (w - x + l) div z;
            virtualCoordinates.y := (h - y + t) div z;
        end;
         3: begin
            virtualCoordinates.x := (h - y + t) div z;
            virtualCoordinates.y := (x - l) div z;
        end;
        end;
        result := true;
    end;

    function TEmulationForm.fileOpen(fileNameAddress, mode: int): int;
    var
        f: UnicodeString;
        e: EmulationOfFileStream;
    begin
        f := readNullTerminatedUnicodeString(fileNameAddress);
        if malikFileCheckAccess(f, (mode and $03) <> $01) then begin
            result := 0;
            exit;
        end;
        e := EmulationOfFileStream.create(self, malikFileNameToOS(f), mode);
        if e.isInvalidHandle() then begin
            result := 0;
            e.free();
            exit;
        end;
        result := addSystemObject(e);
    end;

    function TEmulationForm.fileDelete(fileNameAddress: int): int;
    var
        f: UnicodeString;
    begin
        f := readNullTerminatedUnicodeString(fileNameAddress);
        if malikFileCheckAccess(f, true) then begin
            result := 0;
            exit;
        end;
        if deleteFile(malikFileNameToOS(f)) then begin
            result := 1;
            exit;
        end;
        result := 0;
    end;

    function TEmulationForm.fileFind(fileNameAddress, descriptorAddress: int): int;
    var
        d: boolean;
        l: int;
        h: int;
        r: int;
        s: Windows.WIN32_FIND_DATAW;
        k: UnicodeString;
        n: UnicodeString;
        m: UnicodeString;
    begin
        n := readNullTerminatedUnicodeString(fileNameAddress);
        l := length(n);
        if (l <= 0) or (n[1] <> '/') then begin
            n := '/' + n;
        end;
        if malikFileCheckAccess(n, false) then begin
            result := 0;
            exit;
        end;
        if (l > 0) and (n[l] = '/') then begin
            k := n + '*.*';
            d := true;
        end else begin
            k := n;
            d := false;
        end;
        k := malikFileNameToOS(k);
        m := n;
        initialize(s);
        h := int(Windows.findFirstFileW(PWideChar(k), @s));
        if h = int(Windows.INVALID_HANDLE_VALUE) then begin
            result := 0;
            exit;
        end;
        r := 1;
        if d then begin
            m := n + UnicodeString(s.cFileName);
        end;
        while (r <> 0) and malikFileCheckAccess(m, false) do begin
            r := int(Windows.findNextFileW(h, @s));
            if d then begin
                m := n + UnicodeString(s.cFileName);
            end;
        end;
        if r = 0 then begin
            Windows.findClose(h);
            result := -1;
            exit;
        end;
        writeFileInfoToProcessor(descriptorAddress, s);
        result := addSystemObject(EmulationOfFileEnumeration.create(self, h, d, n));
    end;

    function TEmulationForm.fileMove(sourceFileNameAddress, destinationFileNameAddress: int): int;
    var
        s: UnicodeString;
        d: UnicodeString;
    begin
        s := readNullTerminatedUnicodeString(sourceFileNameAddress);
        d := readNullTerminatedUnicodeString(destinationFileNameAddress);
        if malikFileCheckAccess(s, true) or malikFileCheckAccess(d, true) then begin
            result := 0;
            exit;
        end;
        if move(malikFileNameToOS(s), malikFileNameToOS(d)) then begin
            result := 1;
            exit;
        end;
        result := 0;
    end;

    function TEmulationForm.fileReadAttr(fileNameAddress, descriptorAddress: int): int;
    var
        l: int;
        h: int;
        a: PMalikFileAttributes;
        p: MalikProcessor;
        s: Windows.WIN32_FIND_DATAW;
        f: UnicodeString;
    begin
        f := readNullTerminatedUnicodeString(fileNameAddress);
        l := length(f);
        if (l = 0) or (l > 0) and (f[l] = '/') or malikFileCheckAccess(f, false) then begin
            result := 0;
            exit;
        end;
        f := malikFileNameToOS(f);
        initialize(s);
        h := int(Windows.findFirstFileW(PWideChar(f), @s));
        if h <> int(Windows.INVALID_HANDLE_VALUE) then begin
            Windows.findClose(h);
            p := processor;
            a := PMalikFileAttributes(p.getMemory(descriptorAddress, sizeof(MalikFileAttributes)));
            if a <> nil then begin
                a.creationTime := fileTimeToMalik(s.ftCreationTime);
                a.lastAccessTime := fileTimeToMalik(s.ftLastAccessTime);
                a.lastWriteTime := fileTimeToMalik(s.ftLastWriteTime);
                a.attributes := int(s.dwFileAttributes) and $37;
            end;
            result := 1;
            exit;
        end;
        result := 0;
    end;

    function TEmulationForm.fileWriteAttr(fileNameAddress, descriptorAddress: int): int;
    var
        l: int;
        r: int;
        a: PMalikFileAttributes;
        c: Windows.FILETIME;
        t: Windows.FILETIME;
        w: Windows.FILETIME;
        f: UnicodeString;
    begin
        f := readNullTerminatedUnicodeString(fileNameAddress);
        l := length(f);
        if (l = 0) or (l > 0) and (f[l] = '/') or malikFileCheckAccess(f, true) then begin
            result := 0;
            exit;
        end;
        f := malikFileNameToOS(f);
        a := PMalikFileAttributes(processor.getMemory(descriptorAddress, sizeof(MalikFileAttributes)));
        if a = nil then begin
            result := 0;
            exit;
        end;
        initialize(c);
        initialize(t);
        initialize(w);
        if malikTimeToFile(a.creationTime, @c) and malikTimeToFile(a.lastAccessTime, @t) and malikTimeToFile(a.lastWriteTime, @w) then begin
            r := (int(Windows.getFileAttributesW(PWideChar(f))) and $10) or (a.attributes and $27);
            if int(Windows.setFileAttributesW(PWideChar(f), Dword(r))) = 0 then begin
                result := 0;
                exit;
            end;
            if (r and $10) = 0 then begin
                with FileOutputStream.create(f, true) do begin
                    try
                        if not isInvalidHandle() then begin
                            Windows.setFileTime(getHandle(), @c, @t, @w);
                        end;
                    finally
                        free();
                    end;
                end;
            end;
            result := 1;
            exit;
        end;
        result := 0;
    end;

    function TEmulationForm.directoryCreate(directoryNameAddress: int): int;
    var
        d: UnicodeString;
        l: int;
    begin
        d := readNullTerminatedUnicodeString(directoryNameAddress);
        l := length(d);
        if (l = 0) or (l > 0) and (d[l] = '/') or malikFileCheckAccess(d, true) then begin
            result := 0;
            exit;
        end;
        if createDirectory(malikFileNameToOS(d)) then begin
            result := 1;
            exit;
        end;
        result := 0;
    end;

    function TEmulationForm.directoryDelete(directoryNameAddress: int): int;
    var
        d: UnicodeString;
        l: int;
    begin
        d := readNullTerminatedUnicodeString(directoryNameAddress);
        l := length(d);
        if (l = 0) or (l > 0) and (d[l] = '/') or malikFileCheckAccess(d, true) then begin
            result := 0;
            exit;
        end;
        if deleteDirectory(malikFileNameToOS(d)) then begin
            result := 1;
            exit;
        end;
        result := 0;
    end;

    function TEmulationForm.screenUpdateQuery(): int;
    var
        i: int;
        j: int;
        src: PIntArray;
        srcLine: int;
        srcScanline: int;
        dst: PIntArray;
        dstLine: int;
        dstScanline: int;
    begin
        if renderInProgress or (frames >= maximumFrames) then begin
            result := 0;
            exit;
        end;
        self.sourceImageWidth := self.screenImageWidth;
        self.sourceImageHeight := self.screenImageHeight;
        if (self.sourceImageWidth > 0) and (self.sourceImageHeight > 0) and not sbtnMonitorRecordStart.enabled then begin
            sbtnMonitorRecordStart.enabled := true;
        end;
        j := self.screenImageWidth;
        src := self.screenImagePixels;
        srcLine := 0;
        srcScanline := self.screenImageScanline;
        dst := self.sourceImagePixels;
        dstLine := 0;
        dstScanline := self.sourceImageScanline;
        for i := 0 to self.screenImageHeight - 1 do begin
            opaquePixelsArrayCopy(src, srcLine, dst, dstLine, j);
            inc(srcLine, srcScanline);
            inc(dstLine, dstScanline);
        end;
        postMessage(EM_SCREEN_UPDATE);
        result := 1;
    end;

    function TEmulationForm.screenReadBuffer(): int;
    var
        i: int;
        j: int;
        src: PIntArray;
        srcLine: int;
        srcScanline: int;
        dst: PIntArray;
        dstLine: int;
        dstScanline: int;
    begin
        j := self.screenImageWidth;
        src := self.sourceImagePixels;
        srcLine := 0;
        srcScanline := self.sourceImageScanline;
        dst := self.screenImagePixels;
        dstLine := 0;
        dstScanline := self.screenImageScanline;
        for i := 0 to self.screenImageHeight - 1 do begin
            intArrayCopy(src, srcLine, dst, dstLine, j);
            inc(srcLine, srcScanline);
            inc(dstLine, dstScanline);
        end;
        result := 1;
    end;

    function TEmulationForm.screenGetBuffer(descriptorAddress: int): int;
    var
        descriptor: PMalikGraphicBuffer;
    begin
        descriptor := PMalikGraphicBuffer(processor.getMemory(descriptorAddress, sizeof(MalikGraphicBuffer)));
        if descriptor = nil then begin
            result := 0;
            exit;
        end;
        descriptor.base := screenImageAddress;
        descriptor.scanline := screenImageScanline;
        descriptor.width := short(screenImageWidth);
        descriptor.height := short(screenImageHeight);
        result := 1;
    end;

    function TEmulationForm.screenSetBuffer(descriptorAddress: int): int;
    var
        descriptor: PMalikGraphicBuffer;
        screenPixels: PIntArray;
        screenAddress: int;
        screenScanline: int;
        screenWidth: int;
        screenHeight: int;
    begin
        descriptor := PMalikGraphicBuffer(processor.getMemory(descriptorAddress, sizeof(MalikGraphicBuffer)));
        if (descriptor = nil) then begin
            result := 0;
            exit;
        end;
        screenAddress := descriptor.base;
        screenScanline := descriptor.scanline;
        screenWidth := descriptor.width;
        screenHeight := descriptor.height;
        screenPixels := PIntArray(processor.getMemory(screenAddress, (screenScanline * (screenHeight - 1) + screenWidth) * sizeof(int)));
        if
            (screenPixels = nil) or (screenScanline < screenWidth) or
            (screenWidth < MIN_SCREEN_WIDTH) or (screenWidth > MAX_SCREEN_WIDTH) or
            (screenHeight < MIN_SCREEN_HEIGHT) or (screenHeight > MAX_SCREEN_HEIGHT)
        then begin
            result := 0;
            exit;
        end;
        if (screenWidth <> self.screenImageWidth) or (screenHeight <> self.screenImageHeight) then begin
            resolutionChanged := true;
        end;
        self.screenImagePixels := screenPixels;
        self.screenImageAddress := screenAddress;
        self.screenImageScanline := screenScanline;
        self.screenImageWidth := screenWidth;
        self.screenImageHeight := screenHeight;
        result := 1;
    end;

    function TEmulationForm.screenGetGUIElementMinSizes(element: int): int;
    begin
        result := guiTheme.getSizes(element);
    end;

    function TEmulationForm.screenDrawGUIElement(descriptorAddress: int): int;
    var
        i: int;
        descriptor: PMalikGUIElementDraw;
    begin
        descriptor := PMalikGUIElementDraw(processor.getMemory(descriptorAddress, sizeof(MalikGUIElementDraw)));
        if (descriptor = nil) or (int(descriptor.width) <= 0) or (int(descriptor.height) <= 0) then begin
            result := 0;
            exit;
        end;
        outputGraphicWidth := int(descriptor.dst.width);
        outputGraphicHeight := int(descriptor.dst.height);
        outputGraphicScanline := descriptor.dst.scanline;
        if outputGraphicScanline >= 0 then begin
            outputGraphicPixels := PIntArray(processor.getMemory(descriptor.dst.base, (outputGraphicScanline * (outputGraphicHeight - 1) + outputGraphicWidth) * sizeof(int)));
        end else begin
            i := (-outputGraphicScanline) * (outputGraphicHeight - 1);
            outputGraphicPixels := @(PIntArray(processor.getMemory(descriptor.dst.base - i * sizeof(int), (i + outputGraphicWidth) * sizeof(int)))[i]);
        end;
        outputGraphicWithAlpha := descriptor.dst.supportsAlpha;
        inputGraphicWithAlpha := true;
        if isOutputGraphicInvalid() then begin
            result := 0;
            exit;
        end;
        guiTheme.drawElement(self, descriptor.element, int(descriptor.left), int(descriptor.top), int(descriptor.width), int(descriptor.height));
        result := 1;
    end;

    function TEmulationForm.screenGetSystemColor(index: int): int;
    begin
        result := guiTheme.getSystemColor(index);
    end;

    function TEmulationForm.screenStretchDraw(descriptorAddress: int): int;
    const
        MAX_SIZE = int($1000);
        TO_TRANSFORM: array [$00..$0f] of int =
        (
            TRANSFORM_NONE, TRANSFORM_ROTATE_90, TRANSFORM_ROTATE_180, TRANSFORM_ROTATE_270,
            TRANSFORM_MIRROR, TRANSFORM_MIRROR_ROTATE_90, TRANSFORM_MIRROR_ROTATE_180, TRANSFORM_MIRROR_ROTATE_270,
            TRANSFORM_MIRROR_ROTATE_180, TRANSFORM_MIRROR_ROTATE_270, TRANSFORM_MIRROR, TRANSFORM_MIRROR_ROTATE_90,
            TRANSFORM_ROTATE_180, TRANSFORM_ROTATE_270, TRANSFORM_NONE, TRANSFORM_ROTATE_90
        );
    var
        descriptor: PMalikStretchDraw;
        rotate90: boolean;
        transform: int;
        dstWidth: int;
        dstHeight: int;
        dstScanline: int;
        dstWithAlpha: boolean;
        dstPixels: PIntArray;
        left: int;
        top: int;
        width: int;
        height: int;
        srcWidth: int;
        srcHeight: int;
        srcScanline: int;
        srcWithAlpha: boolean;
        srcPixels: PIntArray;
        src: int_Array1d;

        function isDestinationInvalid(): boolean; inline;
        begin
            result := (dstPixels = nil) or (abs(dstScanline) < dstWidth) or (dstWidth <= 0) or (dstWidth > MAX_SIZE) or (dstHeight <= 0) or (dstHeight > MAX_SIZE);
        end;

        function isSourceInvalid(): boolean; inline;
        begin
            result := (srcPixels = nil) or (srcWidth <= 0) or (srcWidth > MAX_SIZE) or (srcHeight <= 0) or (srcHeight > MAX_SIZE);
        end;

        function isDrawRegionInvalid(): boolean; inline;
        begin
            result := (width <= 0) or (width > MAX_SIZE) or (height <= 0) or (height > MAX_SIZE);
        end;

        function isDrawRegionOutOfBounds(): boolean; inline;
        begin
            result := (left + width <= 0) or (left >= dstWidth) or (top + height <= 0) or (top >= dstHeight);
        end;

        function getSrcPixel(x, y: int): int;
        var
            newX: int;
            newY: int;
        begin
            if x < 0 then begin
                x := 0;
            end;
            if y < 0 then begin
                y := 0;
            end;
            if rotate90 then begin
                if x >= srcHeight then begin
                    x := srcHeight - 1;
                end;
                if y >= srcWidth then begin
                    y := srcWidth - 1;
                end;
            end else begin
                if x >= srcWidth then begin
                    x := srcWidth - 1;
                end;
                if y >= srcHeight then begin
                    y := srcHeight - 1;
                end;
            end;
            case transform of
            TRANSFORM_NONE: begin
                newX := x;
                newY := y;
            end;
            TRANSFORM_ROTATE_90: begin
                newX := y;
                newY := srcHeight - x - 1;
            end;
            TRANSFORM_ROTATE_180: begin
                newX := srcWidth - x - 1;
                newY := srcHeight - y - 1;
            end;
            TRANSFORM_ROTATE_270: begin
                newX := srcWidth - y - 1;
                newY := x;
            end;
            TRANSFORM_MIRROR: begin
                newX := srcWidth - x - 1;
                newY := y;
            end;
            TRANSFORM_MIRROR_ROTATE_90: begin
                newX := srcWidth - y - 1;
                newY := srcHeight - x - 1;
            end;
            TRANSFORM_MIRROR_ROTATE_180: begin
                newX := x;
                newY := srcHeight - y - 1;
            end;
            TRANSFORM_MIRROR_ROTATE_270: begin
                newX := y;
                newY := x;
            end;
            else
                result := 0;
                exit;
            end;
            result := srcPixels[newX + newY * srcScanline];
        end;

        function getSrcAveragePixel(x1, y1, x2, y2: real): int;
        type
            ColorArray = packed array [0..3] of byte;
        var
            i: int;
            x: int;
            y: int;
            x1int: int;
            y1int: int;
            x2int: int;
            y2int: int;
            x1frac: real;
            y1frac: real;
            x2frac: real;
            y2frac: real;
            c: real;
            s: real;
        begin
            result := 0;
            x1int := toInt(x1);
            y1int := toInt(y1);
            x2int := toInt(x2);
            y2int := toInt(y2);
            x1frac := 1.0 - fracPart(x1);
            y1frac := 1.0 - fracPart(y1);
            x2frac := fracPart(x2);
            y2frac := fracPart(y2);
            s := (x2 - x1) * (y2 - y1);
            for i := 0 to 3 do begin
                { угловые пикселы }
                c :=
                    (ColorArray(getSrcPixel(x1int, y1int))[i] and $ff) * x1frac * y1frac +
                    (ColorArray(getSrcPixel(x2int, y1int))[i] and $ff) * x2frac * y1frac +
                    (ColorArray(getSrcPixel(x1int, y2int))[i] and $ff) * x1frac * y2frac +
                    (ColorArray(getSrcPixel(x2int, y2int))[i] and $ff) * x2frac * y2frac
                ;
                { верхние и нижние пикселы }
                for x := x1int + 1 to x2int - 1 do begin
                    c := c +
                        (ColorArray(getSrcPixel(x, y1int))[i] and $ff) * y1frac +
                        (ColorArray(getSrcPixel(x, y2int))[i] and $ff) * y2frac
                    ;
                end;
                { боковые пикселы }
                for y := y1int + 1 to y2int - 1 do begin
                    c := c +
                        (ColorArray(getSrcPixel(x1int, y))[i] and $ff) * x1frac +
                        (ColorArray(getSrcPixel(x2int, y))[i] and $ff) * x2frac
                    ;
                end;
                { внутренние пикселы }
                for y := y1int + 1 to y2int - 1 do for x := x1int + 1 to x2int - 1 do begin
                    c := c +
                        (ColorArray(getSrcPixel(x, y))[i] and $ff)
                    ;
                end;
                { результат }
                ColorArray(result)[i] := byte(round(c / s));
            end;
        end;

        procedure putPixel(x, y, argb: int);
        var
            pixels: PIntArray;
            index: int;
        begin
            if (x >= 0) and (y >= 0) and (x < dstWidth) and (y < dstHeight) then begin
                pixels := dstPixels;
                index := x + y * dstScanline;
                pixels[index] := computePixel(pixels[index], argb, dstWithAlpha, srcWithAlpha);
            end;
        end;

        procedure drawStretch();
        var
            x: int;
            y: int;
            realSrcWidth: real;
            realSrcHeight: real;
            pixelSizeX: real;
            pixelSizeY: real;
            stepX: real;
            stepY: real;
            realX: real;
            realY: real;
            r: real;
        begin
            if rotate90 then begin
                realSrcWidth := toReal(srcHeight);
                realSrcHeight := toReal(srcWidth);
            end else begin
                realSrcWidth := toReal(srcWidth);
                realSrcHeight := toReal(srcHeight);
            end;
            r := toReal(width);
            if r < realSrcWidth then begin
                pixelSizeX := realSrcWidth / r;
                stepX := pixelSizeX;
            end else begin
                pixelSizeX := 1.0;
                if width > 1 then begin
                    stepX := (realSrcWidth - 1.0) / (r - 1.0);
                end else begin
                    stepX := 0.0;
                end;
            end;
            r := toReal(height);
            if r < realSrcHeight then begin
                pixelSizeY := realSrcHeight / r;
                stepY := pixelSizeY;
            end else begin
                pixelSizeY := 1.0;
                if height > 1 then begin
                    stepY := (realSrcHeight - 1.0) / (r - 1.0);
                end else begin
                    stepY := 0.0;
                end;
            end;
            for y := max(0, -top) to min(height, dstHeight - top) - 1 do begin
                realY := y * stepY;
                for x := max(0, -left) to min(width, dstWidth - left) - 1 do begin
                    realX := x * stepX;
                    putPixel(x + left, y + top, getSrcAveragePixel(realX, realY, realX + pixelSizeX, realY + pixelSizeY));
                end;
            end;
        end;

        procedure drawNormal();
        var
            x: int;
            y: int;
        begin
            for y := max(0, -top) to min(height, dstHeight - top) - 1 do begin
                for x := max(0, -left) to min(width, dstWidth - left) - 1 do begin
                    putPixel(x + left, y + top, getSrcPixel(x, y));
                end;
            end;
        end;

        procedure drawPixel();
        var
            x: int;
            y: int;
            pixel: int;
        begin
            pixel := getSrcPixel(0, 0);
            for y := max(top, 0) to min(height + top, dstHeight) - 1 do begin
                for x := max(left, 0) to min(width + left, dstWidth) - 1 do begin
                    putPixel(x, y, pixel);
                end;
            end;
        end;

    var
        i: int;
    begin
        descriptor := PMalikStretchDraw(processor.getMemory(descriptorAddress, sizeof(MalikStretchDraw)));
        if descriptor = nil then begin
            result := 0;
            exit;
        end;
        dstWidth := int(descriptor.dst.width);
        dstHeight := int(descriptor.dst.height);
        dstScanline := descriptor.dst.scanline;
        dstWithAlpha := descriptor.dst.supportsAlpha;
        if dstScanline >= 0 then begin
            dstPixels := PIntArray(processor.getMemory(descriptor.dst.base, (dstScanline * (dstHeight - 1) + dstWidth) * sizeof(int)));
        end else begin
            i := (-dstScanline) * (dstHeight - 1);
            dstPixels := @(PIntArray(processor.getMemory(descriptor.dst.base - i * sizeof(int), (i + dstWidth) * sizeof(int)))[i]);
        end;
        if isDestinationInvalid() then begin
            result := 0;
            exit;
        end;
        left := int(descriptor.left);
        top := int(descriptor.top);
        width := int(descriptor.width);
        height := int(descriptor.height);
        if isDrawRegionInvalid() then begin
            result := 0;
            exit;
        end;
        srcWidth := int(descriptor.src.width);
        srcHeight := int(descriptor.src.height);
        srcScanline := descriptor.src.scanline;
        srcWithAlpha := descriptor.src.supportsAlpha;
        if srcScanline >= 0 then begin
            srcPixels := PIntArray(processor.getMemory(descriptor.src.base, (srcScanline * (srcHeight - 1) + srcWidth) * sizeof(int)));
        end else begin
            i := (-srcScanline) * (srcHeight - 1);
            srcPixels := @(PIntArray(processor.getMemory(descriptor.src.base - i * sizeof(int), (i + srcWidth) * sizeof(int)))[i]);
        end;
        if isSourceInvalid() then begin
            result := 0;
            exit;
        end;
        if isDrawRegionOutOfBounds() then begin
            result := 2;
            exit;
        end;
        src := int_Array1d_create(srcWidth * srcHeight);
        for i := 0 to srcHeight - 1 do begin
            intArrayCopy(srcPixels, i * srcScanline, PIntArray(src), i * srcWidth, srcWidth);
        end;
        srcScanline := srcWidth;
        srcPixels := PIntArray(src);
        transform := TO_TRANSFORM[descriptor.transform and $0f];
        if (srcWidth = 1) and (srcHeight = 1) then begin
            drawPixel();
            result := 1;
            exit;
        end;
        rotate90 := (transform and 1) <> 0;
        if (not rotate90) and (srcWidth = width) and (srcHeight = height) or rotate90 and (srcWidth = height) and (srcHeight = width) then begin
            drawNormal();
            result := 1;
            exit;
        end;
        drawStretch();
        result := 1;
    end;

    function TEmulationForm.screenCharsGetWidth(descriptorAddress: int): int;
    var
        descriptor: PMalikTextDraw;
        textUTF16: PWideChar;
        textUTF32: PIntArray;
        font: UnicodeFont;
        sys: SystemObject;
        emu: TObject;
        i: int;
        l: int;
        c: int;
        d: int;
    begin
        result := 0;
        descriptor := PMalikTextDraw(processor.getMemory(descriptorAddress, sizeof(MalikTextDraw)));
        if (descriptor = nil) or (descriptor.charsCount <= 0) then begin
            exit;
        end;
        sys := getSystemObject(descriptor.handle);
        if sys = nil then begin
            exit;
        end;
        emu := sys.asObject();
        if not (emu is EmulationOfUnicodeFont) then begin
            exit;
        end;
        font := EmulationOfUnicodeFont(emu).getFont();
        l := descriptor.charsCount;
        if (descriptor.style and MIN_INT) <> 0 then begin
            textUTF16 := PWideChar(processor.getMemory(descriptor.charsAddress, l * sizeof(wchar)));
            if textUTF16 = nil then begin
                exit;
            end;
            i := 0;
            while i < l do begin
                c := int(textUTF16[i]);
                inc(i);
                if (i < l) and (c >= $d800) and (c < $dc00) then begin
                    d := int(textUTF16[i]);
                    if (d >= $dc00) and (d < $e000) then begin
                        c := ((c and $03ff) shl $0a) + (d and $03ff) + $00010000;
                        inc(i);
                    end;
                end;
                inc(result, font.getCharacterWidth(c));
            end;
            exit;
        end;
        textUTF32 := PIntArray(processor.getMemory(descriptor.charsAddress, l * sizeof(int)));
        if textUTF32 = nil then begin
            exit;
        end;
        for i := 0 to l - 1 do begin
            inc(result, font.getCharacterWidth(textUTF32[i]));
        end;
    end;

    function TEmulationForm.screenCharsOutput(descriptorAddress: int): int;
    var
        descriptor: PMalikTextDraw;
        textUTF16: PWideChar;
        textUTF32: PIntArray;
        font: UnicodeFont;
        sys: SystemObject;
        emu: TObject;
        i: int;
        l: int;
        c: int;
        d: int;
        s: int;
        x: int;
        y: int;
        color: int;
        underline: boolean;
        strikeout: boolean;
    begin
        result := 0;
        descriptor := PMalikTextDraw(processor.getMemory(descriptorAddress, sizeof(MalikTextDraw)));
        if (descriptor = nil) or (descriptor.charsCount <= 0) then begin
            exit;
        end;
        sys := getSystemObject(descriptor.handle);
        if sys = nil then begin
            exit;
        end;
        emu := sys.asObject();
        if not (emu is EmulationOfUnicodeFont) then begin
            exit;
        end;
        font := EmulationOfUnicodeFont(emu).getFont();
        outputGraphicWidth := int(descriptor.dst.width);
        outputGraphicHeight := int(descriptor.dst.height);
        outputGraphicScanline := descriptor.dst.scanline;
        if outputGraphicScanline >= 0 then begin
            outputGraphicPixels := PIntArray(processor.getMemory(descriptor.dst.base, (outputGraphicScanline * (outputGraphicHeight - 1) + outputGraphicWidth) * sizeof(int)));
        end else begin
            i := (-outputGraphicScanline) * (outputGraphicHeight - 1);
            outputGraphicPixels := @(PIntArray(processor.getMemory(descriptor.dst.base - i * sizeof(int), (i + outputGraphicWidth) * sizeof(int)))[i]);
        end;
        outputGraphicWithAlpha := descriptor.dst.supportsAlpha;
        inputGraphicWithAlpha := true;
        if isOutputGraphicInvalid() then begin
            exit;
        end;
        l := descriptor.charsCount;
        s := descriptor.style;
        x := int(descriptor.x);
        y := int(descriptor.y);
        color := descriptor.color;
        underline := (s and 1) <> 0;
        strikeout := (s and 2) <> 0;
        if (s and MIN_INT) <> 0 then begin
            textUTF16 := PWideChar(processor.getMemory(descriptor.charsAddress, l * sizeof(wchar)));
            if textUTF16 = nil then begin
                exit;
            end;
            i := 0;
            while i < l do begin
                c := int(textUTF16[i]);
                inc(i);
                if (i < l) and (c >= $d800) and (c < $dc00) then begin
                    d := int(textUTF16[i]);
                    if (d >= $dc00) and (d < $e000) then begin
                        c := ((c and $03ff) shl $0a) + (d and $03ff) + $00010000;
                        inc(i);
                    end;
                end;
                inc(result, font.drawCharacter(self, c, result + x, y, color, underline, strikeout));
            end;
            exit;
        end;
        textUTF32 := PIntArray(processor.getMemory(descriptor.charsAddress, l * sizeof(int)));
        if textUTF32 = nil then begin
            exit;
        end;
        for i := 0 to l - 1 do begin
            c := textUTF32[i];
            inc(result, font.drawCharacter(self, c, result + x, y, color, underline, strikeout));
        end;
    end;

    function TEmulationForm.screenFontInstall(fontNameAddress, fileNameAddress: int): int;
    var
        fontName: UnicodeString;
        fileName: UnicodeString;
        e: EmulationOfUnicodeFont;
    begin
        fontName := readNullTerminatedUnicodeString(fontNameAddress);
        fileName := readNullTerminatedUnicodeString(fileNameAddress);
        if malikFileCheckAccess(fileName, false) then begin
            result := 0;
            exit;
        end;
        e := EmulationOfUnicodeFont.create(self, toUTF8String(fontName), toUTF8String(malikFileNameToOS(fileName)));
        if not e.getFont().isEmpty() then begin
            result := addSystemObject(e);
            exit;
        end;
        e.free();
        result := 0;
    end;

    function TEmulationForm.screenFontUninstall(fontHandle: int): int;
    begin
        if (fontHandle <> nextSystemObject(0, EmulationOfUnicodeFont)) and closeSystemObject(fontHandle, EmulationOfUnicodeFont) then begin
            result := 1;
            exit;
        end;
        result := 0;
    end;

    function TEmulationForm.timerSetInterval(milliseconds: long): int;
    begin
        ptimer.setInterval(milliseconds);
        result := 1;
    end;

    function TEmulationForm.timerGetRemainingTime(): long;
    begin
        result := ptimer.getRemainingMilliseconds();
    end;

    function TEmulationForm.inoutGetKeyboardLightState(): int;
    var
        state: TKeyBoardState;
    begin
        Windows.getKeyboardState(state);
        result := (state[VK_NUMLOCK] and $01) or ((state[VK_CAPITAL] and $01) shl 1) or ((state[VK_SCROLL] and $01) shl 2);
    end;

    function TEmulationForm.addSystemObject(obj: SystemObject): int;
    var
        i: int;
        l: int;
        a: SystemObject_Array1d;
    begin
        l := systemObjectsCount;
        a := systemObjects;
        for i := 0 to l - 1 do begin
            if a[i] = nil then begin
                a[i] := obj;
                result := i shl 2;
                exit;
            end;
        end;
        if l = length(a) then begin
            a := SystemObject_Array1d_create((l shl 1) + 1);
            arraycopy(systemObjects, 0, a, 0, l);
            systemObjects := a;
        end;
        a[l] := obj;
        systemObjectsCount := l + 1;
        result := l shl 2;
    end;

    function TEmulationForm.nextSystemObject(handle: int; objClass: TClass): int;
    var
        i: int;
        j: int;
        l: int;
        a: SystemObject_Array1d;
        obj: SystemObject;
    begin
        j := sar(handle, 2);
        l := systemObjectsCount;
        if ((handle and $03) <> 0) or (j < 0) or (j >= l) then begin
            result := 0;
            exit;
        end;
        a := systemObjects;
        obj := a[j];
        if (obj <> nil) and (obj.asObject() is objClass) then begin
            j := (j + 1) mod l;
        end;
        i := j;
        repeat
            obj := a[i];
            if (obj <> nil) and (obj.asObject() is objClass) then begin
                result := i shl 2;
                exit;
            end;
            inc(i);
        until i >= l;
        result := 0;
    end;

    function TEmulationForm.handleSystemObject(func: int; param: long): long;
    var
        longParam: LongRecord absolute param;
        obj: SystemObject;
    begin
        obj := getSystemObject(longParam.lo);
        if obj <> nil then begin
            result := obj.syscall(func, longParam.hi);
            exit;
        end;
        result := -1;
    end;

    function TEmulationForm.getSystemObject(handle: int): SystemObject;
    var
        i: int;
    begin
        i := sar(handle, 2);
        if ((handle and $03) <> 0) or (i < 0) or (i >= systemObjectsCount) then begin
            result := nil;
            exit;
        end;
        result := systemObjects[i];
    end;

    function TEmulationForm.closeSystemObject(handle: int; objClass: TClass): boolean;
    var
        i: int;
        obj: SystemObject;
    begin
        i := sar(handle, 2);
        if ((handle and $03) <> 0) or (i < 0) or (i >= systemObjectsCount) then begin
            result := false;
            exit;
        end;
        obj := systemObjects[i];
        if (obj = nil) or (not (obj.asObject() is objClass)) then begin
            result := false;
            exit;
        end;
        systemObjects[i] := nil;
        result := true;
    end;

    function TEmulationForm.malikFileNameToOS(const fileName: UnicodeString): UnicodeString;
    var
        d: UnicodeString;
        l: int;
    begin
        d := toUTF16String(runningProgrammeInfo.getProgrammeDirectory());
        l := length(fileName);
        if (l > 0) and (fileName[1] = '/') then begin
            result := d + virtualFileNameToOSFileName(copy(fileName, 2, l - 1));
            exit;
        end;
        result := d + virtualFileNameToOSFileName(fileName);
    end;

    function TEmulationForm.malikFileCheckAccess(const fileName: UnicodeString; forWrite: boolean): boolean;
    var
        f: UnicodeString;
        c: wchar;
        i: int;
    begin
        f := toLowerCase(fileName);
        if (length(f) <= 0) or (f[1] <> '/') then begin
            f := '/' + f;
        end;
        { Это чтоб программа по всему логическому диску не шастала… }
        result := (pos('/../', f) > 0) or (pos('/./', f) > 0) or endsWith('/.', f) or endsWith('/..', f);
        if result then begin
            exit;
        end;
        for i := 1 to length(f) do begin
            c := f[i];
            if (c >= #0000) and (c < #0100) and (c in ['\', ':', '*', '?', '"', '<', '>', '|', '%']) then begin
                {
                    Список запрещённых символов в имени файла в операционной системе Windows.
                    Косая черта (/) разрешена, поскольку она используется для разделения папок.
                    Процент (%) запрещён, дабы не дать программам устраивать взбучку с переменными
                    среды операционной системы Windows.
                }
                result := true;
                exit;
            end;
        end;
        { Недоступные (даже для создания) и скрываемые из поисковой выдачи файлы и папки }
        result := (endsWith('.dbg', f) or endsWith('.mal', f)) or
            { Файлы, доступные только для чтения }
            forWrite and (endsWith('/meta-inf/manifest.mf', f) or endsWith('/meta-inf', f) or
            endsWith('.ico', f) or endsWith('.bindbg', f))
        ;
    end;

    procedure TEmulationForm.writeFileInfoToProcessor(descriptorAddress: int; const findData: Windows.WIN32_FIND_DATAW);
    var
        t: Pointer;
        a: PMalikFileInfo;
        f: UnicodeString;
    begin
        a := PMalikFileInfo(processor.getMemory(descriptorAddress, sizeof(MalikFileInfo)));
        if a <> nil then begin
            with a.attributes do begin
                creationTime := fileTimeToMalik(findData.ftCreationTime);
                lastAccessTime := fileTimeToMalik(findData.ftLastAccessTime);
                lastWriteTime := fileTimeToMalik(findData.ftLastWriteTime);
                attributes := int(findData.dwFileAttributes) and $37;
            end;
            a.size := buildLong(int(findData.nFileSizeLow), int(findData.nFileSizeHigh));
            f := UnicodeString(findData.cFileName) + #$0000;
            t := processor.getMemory(a.nameAddress, a.nameLength * sizeof(wchar));
            if t <> nil then begin
                move(f[1], t^, min(a.nameLength, length(f)) * sizeof(wchar));
            end;
        end;
    end;

    procedure TEmulationForm.postMessage(msg: int);
    begin
        while modeChanging do begin
            sleep(2);
        end;
        Windows.postMessage(windowHandle, msg, 0, 0);
    end;

    procedure TEmulationForm.postMessage(msg, wparam, lparam: int);
    begin
        while modeChanging do begin
            sleep(2);
        end;
        Windows.postMessage(windowHandle, msg, wparam, lparam);
    end;

    function TEmulationForm.sendMessage(msg: int): int;
    begin
        while modeChanging do begin
            sleep(2);
        end;
        result := Windows.sendMessage(windowHandle, msg, 0, 0);
    end;

    function TEmulationForm.sendMessage(msg, wparam, lparam: int): int;
    begin
        while modeChanging do begin
            sleep(2);
        end;
        result := Windows.sendMessage(windowHandle, msg, wparam, lparam);
    end;

    procedure TEmulationForm.wndProc(var theMessage: TLMessage);
    begin
        case int(theMessage.msg) of
        WM_SYSCOMMAND: begin
            case theMessage.wParam of
            ID_TOOLBARS: begin
                switchToolBarsVisible();
            end;
            else
                inherited wndProc(theMessage);
            end;
        end;
        WM_ACTIVATE: begin
            windowActivate(theMessage.wParam = WA_ACTIVE);
        end;
        EM_PROCESSOR_BEGIN: begin
            processorBegin();
        end;
        EM_PROCESSOR_END: begin
            processorEnd();
        end;
        EM_DEBUG_BEGIN: begin
            debugBegin();
        end;
        EM_DEBUG_END: begin
            debugEnd();
        end;
        EM_SCREEN_UPDATE: begin
            screenUpdate(true);
        end;
        EM_PLAYER_PCM_CREATE: begin
            theMessage.result := playerPCMCreate(theMessage.wParam, theMessage.lParam);
        end;
        EM_PLAYER_PCM_LOAD_BLOCK: begin
            theMessage.result := playerPCMLoadBlock(theMessage.wParam, theMessage.lParam);
        end;
        EM_PLAYER_PCM_PLAYBACK_CONTROL: begin
            theMessage.result := playerPCMPlayback(theMessage.wParam, theMessage.lParam);
        end;
        EM_PLAYER_PCM_GET_STATE: begin
            theMessage.result := playerPCMGetState(theMessage.wParam);
        end;
        EM_PLAYER_PCM_BLOCK_SEEK: begin
            theMessage.result := playerPCMSetPosition(theMessage.wParam, theMessage.lParam);
        end;
        EM_PLAYER_PCM_GET_BLOCK_INDEX: begin
            long(intToPointer(theMessage.lParam)^) := playerPCMGetPosition(theMessage.wParam);
        end;
        EM_PLAYER_PCM_VOLUME_CONTROL: begin
            theMessage.result := playerPCMVolume(theMessage.wParam, theMessage.lParam);
        end;
        EM_PLAYER_PCM_DESTROY: begin
            theMessage.result := playerPCMDestroy(theMessage.wParam);
        end;
        EM_PLAYER_MIDI_CREATE: begin
            theMessage.result := playerMIDICreate();
        end;
        EM_PLAYER_MIDI_LOAD_BLOCK: begin
            theMessage.result := playerMIDILoadBlock(theMessage.wParam, theMessage.lParam);
        end;
        EM_PLAYER_MIDI_PLAYBACK_CONTROL: begin
            theMessage.result := playerMIDIPlayback(theMessage.wParam, theMessage.lParam);
        end;
        EM_PLAYER_MIDI_GET_STATE: begin
            theMessage.result := playerMIDIGetState(theMessage.wParam);
        end;
        EM_PLAYER_MIDI_BLOCK_SEEK: begin
            theMessage.result := playerMIDISetPosition(theMessage.wParam, theMessage.lParam);
        end;
        EM_PLAYER_MIDI_GET_BLOCK_INDEX: begin
            long(intToPointer(theMessage.lParam)^) := playerMIDIGetPosition(theMessage.wParam);
        end;
        EM_PLAYER_MIDI_VOLUME_CONTROL: begin
            theMessage.result := playerMIDIVolume(theMessage.wParam, theMessage.lParam);
        end;
        EM_PLAYER_MIDI_DESTROY: begin
            theMessage.result := playerMIDIDestroy(theMessage.wParam);
        end
        else
            inherited wndProc(theMessage);
        end;
    end;

    constructor TEmulationForm.create(theOwner: TComponent);
    begin
        inherited create(theOwner);
        createSystemMenu();
        theOwner.getInterface(stringToGUID(MAIN_WINDOW_INTERFACE_GUID), owner);
        closeOnTerminate := false;
        pause := true;
        terminated := false;
        prevWindowState := wsNormal;
    end;

    destructor TEmulationForm.destroy;
    var
        l: int;
        t: int;
        w: int;
        h: int;
        v: int;
    begin
        l := left;
        t := top;
        w := getOutputAreaWidth();
        h := getOutputAreaHeight();
        if panelView.Visible then begin
            v := 1;
        end else begin
            v := 0;
        end;
        owner.setSetting(SECTION_WINDOWS, runningProgrammeID, makeStringOf(toStringArray1d([
            toDecString(screenRotate), toDecString(screenZoom),
            toDecString(l), toDecString(t), toDecString(w),
            toDecString(h), toDecString(v)
        ])));
        owner.saveSettings();
        owner.notifyTerminated(self);
        ptimer.free();
        processor.free();
        context.free();
        outputImage.free();
        scaledImageBitmap.free();
        rotatedImageBitmap.free();
        sourceImageBitmap.free();
        runningProgrammeInfo.free();
        inherited destroy;
    end;

    function TEmulationForm.getClass(): _Class;
    begin
        result := ClassData.create(classType());
    end;

    function TEmulationForm.asObject(): TObject;
    begin
        result := self;
    end;

    procedure TEmulationForm.run();
    var
        processor: MalikProcessor;
        size: int;
    begin
        processor := self.processor;
        if processor.load() then begin
            size := (MAX_SCREEN_WIDTH * MAX_SCREEN_HEIGHT) * sizeof(int);
            screenImageAddress := processor.createRegion(size);
            screenImageScanline := MAX_SCREEN_WIDTH;
            screenImagePixels := PIntArray(processor.getMemory(screenImageAddress, size));
            disassembler.loadData();
            sendMessage(EM_PROCESSOR_BEGIN);
            processor.execute();
            exit;
        end;
        programmeTerminated();
    end;

    procedure TEmulationForm.putPixel(x, y, argb: int);
    var
        pixels: PIntArray;
        index: int;
    begin
        if (x >= 0) and (y >= 0) and (x < outputGraphicWidth) and (y < outputGraphicHeight) then begin
            pixels := outputGraphicPixels;
            index := x + y * outputGraphicScanline;
            pixels[index] := computePixel(pixels[index], argb, outputGraphicWithAlpha, inputGraphicWithAlpha);
        end;
    end;

    procedure TEmulationForm.timerHandle(timer: Timer; millisecondsElapsed: long);
    begin
        if timer = ptimer then begin
            processor.interrupt($18, millisecondsElapsed);
        end;
    end;

    procedure TEmulationForm.loadPCMBlock(player: PlayerPCM; blockIndex: int);
    var
        i: int;
        count: int;
        obj: TObject;
        objects: SystemObject_Array1d;
        sysobj: SystemObject;
    begin
        if processor.isTerminated() then begin
            exit;
        end;
        count := systemObjectsCount;
        objects := systemObjects;
        for i := count - 1 downto 0 do begin
            sysobj := objects[i];
            if sysobj = nil then begin
                continue;
            end;
            obj := sysobj.asObject();
            if (obj is EmulationOfPlayerPCM) and (EmulationOfPlayerPCM(obj).soundPlayer = player) then begin
                processor.interrupt($1e, buildLong(i shl 2, blockIndex));
                exit;
            end;
        end;
    end;

    procedure TEmulationForm.endMIDITrack(player: PlayerMIDI);
    var
        i: int;
        count: int;
        obj: TObject;
        objects: SystemObject_Array1d;
        sysobj: SystemObject;
    begin
        if processor.isTerminated() then begin
            exit;
        end;
        count := systemObjectsCount;
        objects := systemObjects;
        for i := count - 1 downto 0 do begin
            sysobj := objects[i];
            if sysobj = nil then begin
                continue;
            end;
            obj := sysobj.asObject();
            if (obj is EmulationOfPlayerMIDI) and (EmulationOfPlayerMIDI(obj).soundPlayer = player) then begin
                processor.interrupt($1f, buildLong(i shl 2, -1));
                exit;
            end;
        end;
    end;

    procedure TEmulationForm.programmePause();
    begin
        while pause do begin
            sleep(2);
        end;
        pause := true;
    end;

    procedure TEmulationForm.programmeResume();
    begin
        pause := false;
    end;

    procedure TEmulationForm.programmeTerminated();
    begin
        sendMessage(EM_PROCESSOR_END);
    end;

    procedure TEmulationForm.programmeBreakpoint();
    begin
        suspend := true;
        processor.setDebug(true);
    end;

    procedure TEmulationForm.instructionExecuting(contextID: int);
    begin
        self.contextID := contextID;
        if debug or suspend then begin
            processor.getContext(contextID, context);
            if suspend or disassembler.mustBreak(contextID, context.regEIP) then begin
                suspend := false;
                nowDebugging := true;
                sendMessage(EM_DEBUG_BEGIN);
                programmePause();
                sendMessage(EM_DEBUG_END);
                nowDebugging := false;
            end;
        end;
    end;

    function TEmulationForm.getCurrentUTCOffset(): int;
    var
        tz: Windows.TIME_ZONE_INFORMATION;
    begin
        initialize(tz);
        Windows.getTimeZoneInformation(@tz);
        result := -60000 * int(tz.bias);
    end;

    function TEmulationForm.getCurrentUTCTime(): long;
    var
        t: Windows.SYSTEMTIME;
    begin
        initialize(t);
        Windows.getSystemTime(@t);
        result := systemTimeToMalik(t);
    end;

    function TEmulationForm.getMilliseconds(): long;
    begin
        result := Timers.getMilliseconds();
    end;

    function TEmulationForm.syscall(func: int; argument: long): long;
    var
        longParam: LongRecord absolute argument;
    begin
        result := 0;
        case func of
        $0012..$0016, $0019, $0020..$0023, $0029..$002b, $0031..$0034, $0036, $0039..$003c, $003e: begin
            result := handleSystemObject(func, argument);
        end;
        $0010: begin
            result := fileOpen(longParam.lo, longParam.hi);
        end;
        $0011: begin
            closeSystemObject(longParam.lo, EmulationOfFileStream);
        end;
        $0017: begin
            result := fileDelete(longParam.lo);
        end;
        $0018: begin
            result := fileFind(longParam.lo, longParam.hi);
        end;
        $001a: begin
            closeSystemObject(longParam.lo, EmulationOfFileEnumeration);
        end;
        $001b: begin
            result := fileMove(longParam.lo, longParam.hi);
        end;
        $001c: begin
            result := fileReadAttr(longParam.lo, longParam.hi);
        end;
        $001d: begin
            result := fileWriteAttr(longParam.lo, longParam.hi);
        end;
        $001e: begin
            result := directoryCreate(longParam.lo);
        end;
        $001f: begin
            result := directoryDelete(longParam.lo);
        end;
        $0024: begin
            result := screenGetGUIElementMinSizes(longParam.lo);
        end;
        $0025: begin
            result := screenDrawGUIElement(longParam.lo);
        end;
        $0026: begin
            result := screenGetSystemColor(longParam.lo);
        end;
        $0027: begin
            result := screenStretchDraw(longParam.lo);
        end;
        $0028: begin
            result := nextSystemObject(longParam.lo, EmulationOfUnicodeFont);
        end;
        $002c: begin
            result := screenCharsGetWidth(longParam.lo);
        end;
        $002d: begin
            result := screenCharsOutput(longParam.lo);
        end;
        $002e: begin
            result := screenFontInstall(longParam.lo, longParam.hi);
        end;
        $002f: begin
            result := screenFontUninstall(longParam.lo);
        end;
        $0030: begin
            result := sendMessage(EM_PLAYER_PCM_CREATE, longParam.lo, longParam.hi);
        end;
        $0035: begin
            sendMessage(EM_PLAYER_PCM_GET_BLOCK_INDEX, longParam.lo, pointerToInt(@result));
        end;
        $0037: begin
            result := sendMessage(EM_PLAYER_PCM_DESTROY, longParam.lo, 0);
        end;
        $0038: begin
            result := sendMessage(EM_PLAYER_MIDI_CREATE, 0, 0);
        end;
        $003d: begin
            sendMessage(EM_PLAYER_MIDI_GET_BLOCK_INDEX, longParam.lo, pointerToInt(@result));
        end;
        $003f: begin
            result := sendMessage(EM_PLAYER_MIDI_DESTROY, longParam.lo, 0);
        end;
        $0040: begin
            result := timerSetInterval(longParam.value);
        end;
        $0041: begin
            result := timerGetRemainingTime();
        end;
        $0050: begin
            result := 0;
        end;
        $0051: begin
            result := inoutGetKeyboardLightState();
        end;
        end;
    end;

    function TEmulationForm.syscall(func, param: int): long;
    begin
        result := 0;
        case func of
        $0016: begin
            result := long(Windows.MAX_PATH) - long(length(toUTF16String(runningProgrammeInfo.getProgrammeDirectory())));
        end;
        $0020: begin
            result := screenUpdateQuery();
        end;
        $0021: begin
            result := screenReadBuffer();
        end;
        $0022: begin
            result := screenGetBuffer(param);
        end;
        $0023: begin
            result := screenSetBuffer(param);
        end;
        end;
    end;

    procedure TEmulationForm.showEmulationWindow();
    begin
        showOnTop();
        setFocus();
    end;

    procedure TEmulationForm.showDisassemblerWindow();
    var
        d: TDisassemblerForm;
    begin
        if nowDebugging then begin
            d := disassembler;
            d.showOnTop();
            d.setFocus();
            exit;
        end;
        programmeBreakpoint();
    end;

    procedure TEmulationForm.setDebug(debug: boolean);
    var
        p: MalikProcessor;
    begin
        self.debug := debug;
        p := processor;
        if p <> nil then begin
            p.setDebug(debug);
        end;
    end;

    function TEmulationForm.isNowDebugging(): boolean;
    begin
        result := nowDebugging;
    end;

    function TEmulationForm.getMalikProcessor(): MalikProcessor;
    begin
        result := processor;
    end;

    function TEmulationForm.getMainWindow(): MainWindowInterface;
    begin
        result := owner;
    end;

    function TEmulationForm.getProgrammeDirectory(): AnsiString;
    begin
        result := runningProgrammeInfo.getProgrammeDirectory();
    end;

    procedure TEmulationForm.setProgramme(const id: AnsiString; info: ProgrammeInfo);
    var
        execFileName: AnsiString;
        sizes: AnsiString_Array1d;
        pman: ProgrammeManifest;
        width: int;
        height: int;
    begin
        execFileName := info.getExecutableFileName();
        if not fileExists(execFileName) then begin
            raise FileNotOpenException.create('важный исполняемый файл, необходимый для запуска программы, не найден: ' + execFileName);
        end;
        pman := ProgrammeManifest.create();
        try
            info.loadManifest(pman);
            info.loadIcon(icon);
            caption := pman.getValue(MANIFEST_PROPERTY_PROGRAMME_NAME);
            sizes := getComponents(pman.getValue(MANIFEST_PROPERTY_MALIK_SCREEN_SIZE));
        finally
            pman.free();
        end;
        runningProgrammeID := id;
        runningProgrammeInfo := ProgrammeInfo.create(info.getProgrammeDirectory());
        if length(sizes) >= 2 then begin
            screenImageWidth := max(MIN_SCREEN_WIDTH, min(MAX_SCREEN_WIDTH, parseDecInt(sizes[0], DEFAULT_SCREEN_WIDTH)));
            screenImageHeight := max(MIN_SCREEN_HEIGHT, min(MAX_SCREEN_HEIGHT, parseDecInt(sizes[1], DEFAULT_SCREEN_HEIGHT)));
        end else begin
            screenImageWidth := DEFAULT_SCREEN_WIDTH;
            screenImageHeight := DEFAULT_SCREEN_HEIGHT;
        end;
        sizes := getComponents(owner.getSetting(SECTION_WINDOWS, id, ''));
        if length(sizes) >= 6 then begin
            screenRotate := parseDecInt(sizes[0], 0);
            screenZoom := max(1, min(4, parseDecInt(sizes[1], 1)));
            if (screenRotate and 1) = 0 then begin
                width := screenZoom * screenImageWidth;
                height := screenZoom * screenImageHeight;
            end else begin
                width := screenZoom * screenImageHeight;
                height := screenZoom * screenImageWidth;
            end;
            if (length(sizes) > 6) and (parseDecInt(sizes[6], 1) <> 0) then begin
                switchToolBarsVisible();
            end;
            setOutputAreaBounds(parseDecInt(sizes[2], (screen.width - width) div 2), parseDecInt(sizes[3], (screen.height - height) div 2), parseDecInt(sizes[4], width), parseDecInt(sizes[5], height));
        end else begin
            screenRotate := 0;
            screenZoom := 1;
            width := screenImageWidth;
            height := screenImageHeight;
            setOutputAreaBounds((screen.width - width) div 2, (screen.height - height) div 2, width, height);
        end;
        showScreenZoomStatus(screenZoom);
        sbtnViewZoomIn.enabled := screenZoom < 4;
        sbtnViewZoomOut.enabled := screenZoom > 1;
        sbtnMonitorAdjustSize.enabled := true;
        sbtnMonitorFullScreen.enabled := false;
        sbtnMonitorRecordStart.enabled := false;
        sbtnMonitorRecordStop.enabled := false;
    end;

    procedure TEmulationForm.runProgramme();
    var
        a: AnsiString;
        s: int;
        i: int;
        j: int;
        font: AnsiString;
        fonts: UnicodeString_Array1d;
        fontsCount: int;
        disassembler: TDisassemblerForm;
    begin
        { поиск шрифтов }
        a := getEmulatorDirectory() + DIRECTORY_FONTS + DIRECTORY_SEPARATOR;
        j := length(a) + 1;
        fonts := enumerateFiles(a, 'ufn');
        fontsCount := length(fonts);
        if fontsCount = 0 then begin
            raise FontsNotFoundException.create('Не найдено ни одного шрифта UFN. Проверьте папку ' + DIRECTORY_FONTS + ' в папке с эмулятором на наличие шрифтов UFN.');
        end;
        sortStrings(fonts);
        systemObjects := SystemObject_Array1d_create(1);
        addSystemObject(self);
        for i := 0 to fontsCount - 1 do begin
            font := toUTF8String(fonts[i]);
            addSystemObject(EmulationOfUnicodeFont.create(self, copy(font, j, length(font) - j - 3), font));
        end;
        { определение алгоритма масштабирования пиксельной графики }
        a := toLowerCase(owner.getSetting(SECTION_EMULATOR, KEY_GRAPHIC_SCALING, ''));
        if a = 'near' then begin
            scalingAlgorithm := PixelGraphicNearNeighbour.Algorithm.create();
        end else
        if a = 'scalenx' then begin
            scalingAlgorithm := PixelGraphicSimpleScaling.Algorithm.create();
        end else
        if a = 'hqnx' then begin
            scalingAlgorithm := PixelGraphicHighQualityScaling.Algorithm.create();
        end else
        if a = 'standard' then begin
            scalingAlgorithm := PixelGraphicPhotographicScaling.Algorithm.create();
        end else begin
            scalingAlgorithm := nil;
        end;
        { определение максимального количества кадров в секунду }
        s := parseDecInt(owner.getSetting(SECTION_EMULATOR, KEY_MAXIMUM_FPS, ''), 25);
        maximumFrames := min(max(1, s), 100);
        { определение темы для графического пользовательского интерфейса }
        guiTheme := getTheme(owner.getSetting(SECTION_EMULATOR, KEY_THEME, ''));
        if guiTheme = nil then begin
            guiTheme := getTheme(0);
        end;
        { создание промежуточных изображений }
        sourceImageBitmap := TBitmap.create();
        sourceImagePixels := createPixelImage(sourceImageBitmap, MAX_SCREEN_WIDTH, MAX_SCREEN_HEIGHT);
        sourceImagePixels := @(sourceImagePixels[(MAX_SCREEN_HEIGHT - 1) * MAX_SCREEN_WIDTH]);
        sourceImageScanline := -MAX_SCREEN_WIDTH;
        s := max(MAX_SCREEN_WIDTH, MAX_SCREEN_HEIGHT);
        rotatedImageBitmap := TBitmap.create();
        rotatedImagePixels := createPixelImage(rotatedImageBitmap, s, s);
        rotatedImagePixels := @(rotatedImagePixels[(s - 1) * s]);
        rotatedImageScanline := -s;
        scaledImageBitmap := TBitmap.create();
        outputImage := TBitmap.create();
        { создание среды выполнения программы }
        windowHandle := handle;
        a := runningProgrammeInfo.getExecutableFileName();
        context := MalikDebugContext.create();
        ptimer := Timer.create(self);
        processor := MalikProcessor.create(toUTF16String(a), self);
        processor.setDebug(debug);
        suspend := debug;
        disassembler := TDisassemblerForm.create(self);
        self.disassembler := disassembler;
        disassembler.icon := icon;
        disassembler.caption := disassembler.caption + caption;
        { запуск }
        startThread(self);
    end;

    procedure TEmulationForm.showExecutionStatus(const statusText: AnsiString);
    begin
        status.panels.items[0].text := statusText;
    end;

    procedure TEmulationForm.showRecordingStatus(const statusText: AnsiString);
    begin
        status.panels.items[1].text := statusText;
    end;

    procedure TEmulationForm.showScreenZoomStatus(screenZoom: int);
    begin
        status.panels.items[2].text := 'x' + toDecString(screenZoom);
    end;

    procedure TEmulationForm.showFramesPerSecondStatus(fps: int);
    begin
        status.panels.Items[3].text := 'FPS: ' + toDecString(fps);
    end;
{%endregion}

{%region EmulationOfFileStream }
    constructor EmulationOfFileStream.create(TEmulationForm_self: TEmulationForm; const fileName: UnicodeString; mode: int);
    var
        fo: FileOutputStream;
        fi: FileInputStream;
        fs: FileStream;
    begin
        inherited create(TEmulationForm_self);
        case int(mode and $03) of
        $00: begin
            fo := FileOutputStream.create(fileName, true);
            invalidHandle := fo.isInvalidHandle();
            fileOutput := fo;
        end;
        $01: begin
            fi := FileInputStream.create(fileName);
            invalidHandle := fi.isInvalidHandle();
            fileInput := fi;
        end;
        $02: begin
            fo := FileOutputStream.create(fileName, false);
            invalidHandle := fo.isInvalidHandle();
            fileOutput := fo;
        end;
        $03: begin
            fs := FileStream.create(fileName);
            invalidHandle := fs.isInvalidHandle();
            fileInput := fs;
            fileOutput := fs;
        end;
        end;
    end;

    function EmulationOfFileStream.write(param: int): int;
    const
        BUF_SIZE = int(1024);
    var
        f: Output;
        p: MalikProcessor;
        d: PMalikDataDescriptor;
        b: byte_Array1d;
        a: int;
        c: int;
        s: int;
        t: Pointer;
    begin
        f := fileOutput;
        if f = nil then begin
            result := -1;
            exit;
        end;
        p := TEmulationForm_self.getMalikProcessor();
        d := PMalikDataDescriptor(p.getMemory(param, sizeof(MalikDataDescriptor)));
        if d = nil then begin
            result := -1;
            exit;
        end;
        b := nil;
        a := d.address;
        c := d.count;
        result := 0;
        while c > 0 do begin
            if b = nil then begin
                b := byte_Array1d_create(BUF_SIZE);
            end;
            s := min(c, BUF_SIZE);
            t := p.getMemory(a, s);
            if t = nil then begin
                exit;
            end;
            move(t^, b[0], s);
            s := f.write(b, 0, s);
            if s <= 0 then begin
                exit;
            end;
            dec(c, s);
            inc(a, s);
            inc(result, s);
        end;
    end;

    function EmulationOfFileStream.read(param: int): int;
    const
        BUF_SIZE = int(1024);
    var
        f: Input;
        p: MalikProcessor;
        d: PMalikDataDescriptor;
        b: byte_Array1d;
        a: int;
        c: int;
        s: int;
        t: Pointer;
    begin
        f := fileInput;
        if f = nil then begin
            result := -1;
            exit;
        end;
        p := TEmulationForm_self.getMalikProcessor();
        d := PMalikDataDescriptor(p.getMemory(param, sizeof(MalikDataDescriptor)));
        if d = nil then begin
            result := -1;
            exit;
        end;
        b := nil;
        a := d.address;
        c := d.count;
        result := 0;
        while c > 0 do begin
            if b = nil then begin
                b := byte_Array1d_create(BUF_SIZE);
            end;
            s := min(c, BUF_SIZE);
            t := p.getMemory(a, s);
            if t = nil then begin
                exit;
            end;
            s := f.read(b, 0, s);
            if s <= 0 then begin
                exit;
            end;
            move(b[0], t^, s);
            dec(c, s);
            inc(a, s);
            inc(result, s);
        end;
    end;

    function EmulationOfFileStream.seek(param: int): int;
    var
        f: Input;
        p: MalikProcessor;
        t: Pointer;
        d: long;
    begin
        f := fileInput;
        if f = nil then begin
            result := -1;
            exit;
        end;
        p := TEmulationForm_self.getMalikProcessor();
        t := p.getMemory(param, sizeof(long));
        if t = nil then begin
            result := -1;
            exit;
        end;
        d := long(t^);
        result := f.seek(d);
    end;

    function EmulationOfFileStream.size(): long;
    var
        f: Input;
    begin
        f := fileInput;
        if f = nil then begin
            result := -1;
            exit;
        end;
        result := f.size();
    end;

    function EmulationOfFileStream.truncate(): long;
    var
        f: _Interface;
    begin
        f := fileInput;
        if (f = nil) or (fileOutput = nil) then begin
            result := -1;
            exit;
        end;
        result := FileStream(f.asObject()).truncate();
    end;

    function EmulationOfFileStream.syscall(func, param: int): long;
    begin
        result := -1;
        case func of
        $0012: begin
            result := read(param);
        end;
        $0013: begin
            result := write(param);
        end;
        $0014: begin
            result := seek(param);
        end;
        $0015: begin
            result := size();
        end;
        $0016: begin
            result := truncate();
        end;
        end;
    end;

    function EmulationOfFileStream.isInvalidHandle(): boolean;
    begin
        result := invalidHandle;
    end;
{%endregion}

{%region EmulationOfFileEnumeration }
    constructor EmulationOfFileEnumeration.create(TEmulationForm_self: TEmulationForm; handle: int; directory: boolean; const fileName: UnicodeString);
    begin
        inherited create(TEmulationForm_self);
        self.handle := handle;
        self.directory := directory;
        self.fileName := fileName;
    end;

    destructor EmulationOfFileEnumeration.destroy;
    begin
        Windows.findClose(handle);
        inherited destroy;
    end;

    function EmulationOfFileEnumeration.findNext(param: int): int;
    var
        f: TEmulationForm;
        d: boolean;
        h: int;
        r: int;
        s: Windows.WIN32_FIND_DATAW;
        n: UnicodeString;
        m: UnicodeString;
    begin
        f := TEmulationForm_self;
        d := directory;
        h := handle;
        n := fileName;
        m := n;
        initialize(s);
        result := int(Windows.findNextFileW(h, @s));
        if result = 0 then begin
            exit;
        end;
        r := 1;
        if d then begin
            m := n + UnicodeString(s.cFileName);
        end;
        while (r <> 0) and f.malikFileCheckAccess(m, false) do begin
            r := int(Windows.findNextFileW(h, @s));
            if d then begin
                m := n + UnicodeString(s.cFileName);
            end;
        end;
        if r = 0 then begin
            result := 0;
            exit;
        end;
        f.writeFileInfoToProcessor(param, s);
        result := 1;
    end;

    function EmulationOfFileEnumeration.syscall(func, param: int): long;
    begin
        result := -1;
        if func = $0019 then begin
            result := findNext(param);
        end;
    end;
{%endregion}

{%region EmulationOfUnicodeFont }
    constructor EmulationOfUnicodeFont.create(TEmulationForm_self: TEmulationForm; const fontName, fileName: AnsiString);
    begin
        inherited create(TEmulationForm_self);
        self.font := UnicodeFont.create(fontName, fileName);
    end;

    destructor EmulationOfUnicodeFont.destroy;
    begin
        font.free();
        inherited destroy;
    end;

    function EmulationOfUnicodeFont.getNameAndLength(nameDestAddress: int): int;
    var
        s: UnicodeString;
        p: Pointer;
    begin
        s := font.getName();
        result := length(s);
        p := TEmulationForm_self.getMalikProcessor().getMemory(nameDestAddress, result * sizeof(wchar));
        if p <> nil then begin
            move(s[1], p^, result * sizeof(wchar));
        end;
    end;

    function EmulationOfUnicodeFont.getFontInfo(): int;
    var
        font: UnicodeFont;
    begin
        result := 0;
        font := self.font;
        if font.isBold() then begin
            result := result or $01;
        end;
        if font.isItalic() then begin
            result := result or $02;
        end;
        result := result or ((font.getHeight() and $ff) shl 8) or ((font.getBaseLineHeight() and $ff) shl 16);
    end;

    function EmulationOfUnicodeFont.getCharSupport(code: int): int;
    begin
        if font.isCharacterExists(code) then begin
            result := 1;
            exit;
        end;
        result := 0;
    end;

    function EmulationOfUnicodeFont.syscall(func, param: int): long;
    begin
        result := -1;
        case func of
        $0029: begin
            result := getNameAndLength(param);
        end;
        $002a: begin
            result := getFontInfo();
        end;
        $002b: begin
            result := getCharSupport(param);
        end;
        end;
    end;

    function EmulationOfUnicodeFont.getFont(): UnicodeFont;
    begin
        result := font;
    end;
{%endregion}

{%region EmulationOfPlayer }
    constructor EmulationOfPlayer.create(TEmulationForm_self: TEmulationForm; soundPlayer: Player);
    begin
        inherited create(TEmulationForm_self);
        self.soundPlayer := soundPlayer;
    end;

    destructor EmulationOfPlayer.destroy;
    begin
        soundPlayer.free();
        inherited destroy;
    end;

    function EmulationOfPlayer.syscall(func, param: int): long;
    begin
        result := TEmulationForm_self.sendMessage(TEmulationForm_self.EM_SYSCALL + func, handle, param);
    end;
{%endregion}

{%region EmulationOfPlayerPCM }
    constructor EmulationOfPlayerPCM.create(TEmulationForm_self: TEmulationForm; soundPlayer: PlayerPCM);
    begin
        inherited create(TEmulationForm_self, soundPlayer);
    end;
{%endregion}

{%region EmulationOfPlayerMIDI }
    constructor EmulationOfPlayerMIDI.create(TEmulationForm_self: TEmulationForm; soundPlayer: PlayerMIDI);
    begin
        inherited create(TEmulationForm_self, soundPlayer);
    end;
{%endregion}

initialization {%region}
    TEmulationForm.STR_TOOLBARS := UnicodeString(toUTF16String('Панели инструментов'));
{%endregion}

end.

