460 lines
10 KiB
PHP
460 lines
10 KiB
PHP
; =============================================================================
|
||
; Encoding Conversion - UTF-8 ↔ UTF-16LE
|
||
; Based on Linux VirtualBox Guest Additions implementation
|
||
; =============================================================================
|
||
|
||
; =============================================================================
|
||
; ПРОЦЕДУРА: utf8_to_utf16le
|
||
; Конвертация UTF-8 в UTF-16LE (как требует VirtualBox)
|
||
; Вход:
|
||
; utf8_ptr - указатель на UTF-8 строку
|
||
; utf8_len - длина UTF-8 строки в байтах (без null-terminator)
|
||
; utf16_ptr - указатель на выходной буфер UTF-16LE
|
||
; utf16_max - максимальный размер выходного буфера в байтах
|
||
; Возврат:
|
||
; EAX = количество байт записанных в UTF-16LE (включая BOM если есть)
|
||
; или -1 при ошибке
|
||
; =============================================================================
|
||
proc utf8_to_utf16le uses ebx ecx edx esi edi, \
|
||
utf8_ptr:dword, utf8_len:dword, utf16_ptr:dword, utf16_max:dword
|
||
|
||
DEBUGF 2, "[enc] >>> utf8_to_utf16le(src=0x%x, len=%d, dst=0x%x, max=%d)\n", \
|
||
[utf8_ptr], [utf8_len], [utf16_ptr], [utf16_max]
|
||
|
||
mov esi, [utf8_ptr]
|
||
test esi, esi
|
||
jz .error
|
||
|
||
mov edi, [utf16_ptr]
|
||
test edi, edi
|
||
jz .error
|
||
|
||
mov ecx, [utf8_len]
|
||
test ecx, ecx
|
||
jz .add_null
|
||
|
||
mov edx, [utf16_max]
|
||
cmp edx, 4 ; Минимум для BOM + null
|
||
jb .error
|
||
|
||
push edi ; Сохраним начало буфера
|
||
|
||
; Добавляем UTF-16LE BOM (0xFEFF)
|
||
mov word [edi], 0xFEFF
|
||
add edi, 2
|
||
sub edx, 2
|
||
|
||
.loop:
|
||
test ecx, ecx
|
||
jz .add_null
|
||
|
||
cmp edx, 4 ; Нужно место для символа + null
|
||
jb .buffer_full
|
||
|
||
; Читаем первый байт UTF-8
|
||
movzx eax, byte [esi]
|
||
inc esi
|
||
dec ecx
|
||
|
||
; Проверяем тип символа
|
||
cmp al, 0x80
|
||
jb .ascii ; 0xxxxxxx - ASCII (1 байт)
|
||
|
||
cmp al, 0xE0
|
||
jb .two_byte ; 110xxxxx - 2 байта
|
||
|
||
cmp al, 0xF0
|
||
jb .three_byte ; 1110xxxx - 3 байта
|
||
|
||
cmp al, 0xF8
|
||
jb .four_byte ; 11110xxx - 4 байта
|
||
|
||
; Неправильный UTF-8 - заменяем на replacement character
|
||
mov word [edi], 0xFFFD
|
||
add edi, 2
|
||
sub edx, 2
|
||
jmp .loop
|
||
|
||
.ascii:
|
||
; ASCII символ - прямая конвертация
|
||
mov word [edi], ax
|
||
add edi, 2
|
||
sub edx, 2
|
||
jmp .loop
|
||
|
||
.two_byte:
|
||
; 110xxxxx 10xxxxxx
|
||
test ecx, ecx
|
||
jz .incomplete
|
||
|
||
and eax, 0x1F ; Берем 5 бит
|
||
shl eax, 6
|
||
|
||
movzx ebx, byte [esi]
|
||
inc esi
|
||
dec ecx
|
||
|
||
and ebx, 0x3F ; Берем 6 бит
|
||
or eax, ebx
|
||
|
||
mov word [edi], ax
|
||
add edi, 2
|
||
sub edx, 2
|
||
jmp .loop
|
||
|
||
.three_byte:
|
||
; 1110xxxx 10xxxxxx 10xxxxxx
|
||
cmp ecx, 2
|
||
jb .incomplete
|
||
|
||
and eax, 0x0F ; Берем 4 бита
|
||
shl eax, 12
|
||
|
||
movzx ebx, byte [esi]
|
||
inc esi
|
||
dec ecx
|
||
and ebx, 0x3F
|
||
shl ebx, 6
|
||
or eax, ebx
|
||
|
||
movzx ebx, byte [esi]
|
||
inc esi
|
||
dec ecx
|
||
and ebx, 0x3F
|
||
or eax, ebx
|
||
|
||
mov word [edi], ax
|
||
add edi, 2
|
||
sub edx, 2
|
||
jmp .loop
|
||
|
||
.four_byte:
|
||
; 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||
; Это символ вне BMP - нужна surrogate pair
|
||
cmp ecx, 3
|
||
jb .incomplete
|
||
|
||
cmp edx, 6 ; Нужно 4 байта для surrogate pair + null
|
||
jb .buffer_full
|
||
|
||
and eax, 0x07 ; Берем 3 бита
|
||
shl eax, 18
|
||
|
||
movzx ebx, byte [esi]
|
||
inc esi
|
||
dec ecx
|
||
and ebx, 0x3F
|
||
shl ebx, 12
|
||
or eax, ebx
|
||
|
||
movzx ebx, byte [esi]
|
||
inc esi
|
||
dec ecx
|
||
and ebx, 0x3F
|
||
shl ebx, 6
|
||
or eax, ebx
|
||
|
||
movzx ebx, byte [esi]
|
||
inc esi
|
||
dec ecx
|
||
and ebx, 0x3F
|
||
or eax, ebx
|
||
|
||
; Преобразуем в surrogate pair
|
||
sub eax, 0x10000
|
||
|
||
; High surrogate: 0xD800 + (code >> 10)
|
||
mov ebx, eax
|
||
shr ebx, 10
|
||
add ebx, 0xD800
|
||
mov word [edi], bx
|
||
add edi, 2
|
||
sub edx, 2
|
||
|
||
; Low surrogate: 0xDC00 + (code & 0x3FF)
|
||
and eax, 0x3FF
|
||
add eax, 0xDC00
|
||
mov word [edi], ax
|
||
add edi, 2
|
||
sub edx, 2
|
||
jmp .loop
|
||
|
||
.incomplete:
|
||
; Неполная последовательность - заменяем на replacement
|
||
mov word [edi], 0xFFFD
|
||
add edi, 2
|
||
sub edx, 2
|
||
jmp .add_null
|
||
|
||
.add_null:
|
||
; Добавляем null-terminator
|
||
cmp edx, 2
|
||
jb .buffer_full
|
||
|
||
mov word [edi], 0
|
||
add edi, 2
|
||
|
||
; Вычисляем размер
|
||
pop eax ; Начало буфера
|
||
sub edi, eax
|
||
mov eax, edi
|
||
|
||
DEBUGF 2, "[enc] Converted: %d bytes UTF-16LE (with BOM)\n", eax
|
||
ret
|
||
|
||
.buffer_full:
|
||
pop eax ; Cleanup stack
|
||
DEBUGF 1, "[enc] ERROR: Output buffer too small\n"
|
||
mov eax, -1
|
||
ret
|
||
|
||
.error:
|
||
DEBUGF 1, "[enc] ERROR: Invalid parameters\n"
|
||
mov eax, -1
|
||
ret
|
||
endp
|
||
|
||
; =============================================================================
|
||
; ПРОЦЕДУРА: utf16le_to_utf8
|
||
; Конвертация UTF-16LE в UTF-8
|
||
; Вход:
|
||
; utf16_ptr - указатель на UTF-16LE строку
|
||
; utf16_len - длина UTF-16LE в байтах (включая BOM если есть)
|
||
; utf8_ptr - указатель на выходной буфер UTF-8
|
||
; utf8_max - максимальный размер выходного буфера в байтах
|
||
; Возврат:
|
||
; EAX = количество байт записанных в UTF-8 (без null-terminator)
|
||
; или -1 при ошибке
|
||
; =============================================================================
|
||
proc utf16le_to_utf8 uses ebx ecx edx esi edi, \
|
||
utf16_ptr:dword, utf16_len:dword, utf8_ptr:dword, utf8_max:dword
|
||
|
||
DEBUGF 2, "[enc] >>> utf16le_to_utf8(src=0x%x, len=%d, dst=0x%x, max=%d)\n", \
|
||
[utf16_ptr], [utf16_len], [utf8_ptr], [utf8_max]
|
||
|
||
mov esi, [utf16_ptr]
|
||
test esi, esi
|
||
jz .error
|
||
|
||
mov edi, [utf8_ptr]
|
||
test edi, edi
|
||
jz .error
|
||
|
||
mov ecx, [utf16_len]
|
||
test ecx, ecx
|
||
jz .add_null
|
||
|
||
; Длина должна быть четной
|
||
test ecx, 1
|
||
jnz .error
|
||
|
||
mov edx, [utf8_max]
|
||
test edx, edx
|
||
jz .error
|
||
|
||
push edi ; Сохраним начало буфера
|
||
|
||
; Проверяем и пропускаем BOM если есть
|
||
cmp word [esi], 0xFEFF
|
||
jne .no_bom
|
||
|
||
add esi, 2
|
||
sub ecx, 2
|
||
|
||
.no_bom:
|
||
shr ecx, 1 ; Конвертируем байты в UTF-16 символы
|
||
|
||
.loop:
|
||
test ecx, ecx
|
||
jz .add_null
|
||
|
||
cmp edx, 4 ; Минимум для UTF-8 символа (макс 4 байта)
|
||
jb .buffer_full
|
||
|
||
; Читаем UTF-16 символ (little-endian)
|
||
movzx eax, word [esi]
|
||
add esi, 2
|
||
dec ecx
|
||
|
||
; Проверяем является ли это high surrogate
|
||
cmp ax, 0xD800
|
||
jb .not_surrogate
|
||
cmp ax, 0xDBFF
|
||
ja .check_low_surrogate
|
||
|
||
; High surrogate - нужна low surrogate
|
||
test ecx, ecx
|
||
jz .incomplete
|
||
|
||
movzx ebx, word [esi]
|
||
add esi, 2
|
||
dec ecx
|
||
|
||
; Проверяем low surrogate
|
||
cmp bx, 0xDC00
|
||
jb .invalid_surrogate
|
||
cmp bx, 0xDFFF
|
||
ja .invalid_surrogate
|
||
|
||
; Комбинируем surrogates
|
||
sub eax, 0xD800
|
||
shl eax, 10
|
||
sub ebx, 0xDC00
|
||
add eax, ebx
|
||
add eax, 0x10000
|
||
jmp .encode_utf8
|
||
|
||
.check_low_surrogate:
|
||
cmp ax, 0xDC00
|
||
jb .not_surrogate
|
||
cmp ax, 0xDFFF
|
||
ja .not_surrogate
|
||
|
||
; Одиночная low surrogate - ошибка
|
||
jmp .invalid_surrogate
|
||
|
||
.not_surrogate:
|
||
; Обычный BMP символ
|
||
|
||
.encode_utf8:
|
||
; EAX содержит Unicode code point
|
||
|
||
cmp eax, 0x80
|
||
jb .utf8_1byte
|
||
|
||
cmp eax, 0x800
|
||
jb .utf8_2byte
|
||
|
||
cmp eax, 0x10000
|
||
jb .utf8_3byte
|
||
|
||
; 4-byte UTF-8
|
||
cmp edx, 4
|
||
jb .buffer_full
|
||
|
||
mov ebx, eax
|
||
shr ebx, 18
|
||
and ebx, 0x07
|
||
or bl, 0xF0
|
||
mov byte [edi], bl
|
||
inc edi
|
||
dec edx
|
||
|
||
mov ebx, eax
|
||
shr ebx, 12
|
||
and ebx, 0x3F
|
||
or bl, 0x80
|
||
mov byte [edi], bl
|
||
inc edi
|
||
dec edx
|
||
|
||
mov ebx, eax
|
||
shr ebx, 6
|
||
and ebx, 0x3F
|
||
or bl, 0x80
|
||
mov byte [edi], bl
|
||
inc edi
|
||
dec edx
|
||
|
||
and eax, 0x3F
|
||
or al, 0x80
|
||
mov byte [edi], al
|
||
inc edi
|
||
dec edx
|
||
jmp .loop
|
||
|
||
.utf8_3byte:
|
||
cmp edx, 3
|
||
jb .buffer_full
|
||
|
||
mov ebx, eax
|
||
shr ebx, 12
|
||
and ebx, 0x0F
|
||
or bl, 0xE0
|
||
mov byte [edi], bl
|
||
inc edi
|
||
dec edx
|
||
|
||
mov ebx, eax
|
||
shr ebx, 6
|
||
and ebx, 0x3F
|
||
or bl, 0x80
|
||
mov byte [edi], bl
|
||
inc edi
|
||
dec edx
|
||
|
||
and eax, 0x3F
|
||
or al, 0x80
|
||
mov byte [edi], al
|
||
inc edi
|
||
dec edx
|
||
jmp .loop
|
||
|
||
.utf8_2byte:
|
||
cmp edx, 2
|
||
jb .buffer_full
|
||
|
||
mov ebx, eax
|
||
shr ebx, 6
|
||
and ebx, 0x1F
|
||
or bl, 0xC0
|
||
mov byte [edi], bl
|
||
inc edi
|
||
dec edx
|
||
|
||
and eax, 0x3F
|
||
or al, 0x80
|
||
mov byte [edi], al
|
||
inc edi
|
||
dec edx
|
||
jmp .loop
|
||
|
||
.utf8_1byte:
|
||
; ASCII
|
||
mov byte [edi], al
|
||
inc edi
|
||
dec edx
|
||
jmp .loop
|
||
|
||
.invalid_surrogate:
|
||
.incomplete:
|
||
; Замена на replacement character (U+FFFD = EF BF BD в UTF-8)
|
||
cmp edx, 3
|
||
jb .buffer_full
|
||
|
||
mov byte [edi], 0xEF
|
||
mov byte [edi+1], 0xBF
|
||
mov byte [edi+2], 0xBD
|
||
add edi, 3
|
||
sub edx, 3
|
||
jmp .loop
|
||
|
||
.add_null:
|
||
; Добавляем null-terminator
|
||
cmp edx, 1
|
||
jb .buffer_full
|
||
|
||
mov byte [edi], 0
|
||
inc edi
|
||
|
||
; Вычисляем размер (без null)
|
||
pop eax ; Начало буфера
|
||
sub edi, eax
|
||
dec edi ; Не считаем null
|
||
mov eax, edi
|
||
|
||
DEBUGF 2, "[enc] Converted: %d bytes UTF-8\n", eax
|
||
ret
|
||
|
||
.buffer_full:
|
||
pop eax ; Cleanup stack
|
||
DEBUGF 1, "[enc] ERROR: Output buffer too small\n"
|
||
mov eax, -1
|
||
ret
|
||
|
||
.error:
|
||
DEBUGF 1, "[enc] ERROR: Invalid parameters\n"
|
||
mov eax, -1
|
||
ret
|
||
endp
|