300 lines
9.5 KiB
Plaintext
300 lines
9.5 KiB
Plaintext
from stdint import *
|
|
import stdlib
|
|
import string
|
|
import memhub
|
|
import win32.win32file as wf
|
|
import win32.win32base as wb
|
|
|
|
|
|
# ============================================================
|
|
# VFs - path / file / generic byte-string and encoding helpers
|
|
#
|
|
# Generic bytes: CopyRaw / DupStr / StrEqNoCase.
|
|
# Path: JoinPath / PathIsDir / PathIsFile.
|
|
# Encoding: Utf8ToAnsi / AnsiToUtf8 / WideToUtf8Needed / WideToUtf8.
|
|
# Files: ReadFull / ReadWholeFile / WriteFileAll.
|
|
#
|
|
# Win32 self-declared (no repo declaration; x64 calling convention):
|
|
# kernel32!WideCharToMultiByte: int WideCharToMultiByte(UINT,DWORD,LPCWCH,int,LPSTR,int,LPCCH,LPBOOL);
|
|
# Declared per Win32 API docs / winnls.h.
|
|
# ============================================================
|
|
|
|
define PATH_BUF_CAP = 512 # single dir/file path buffer
|
|
define CONV_CAP = 2048 # encoding conversion temp buffer
|
|
define INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF # GetFileAttributesA failure return
|
|
define CP_UTF8 = 65001 # Win32 code page UTF-8
|
|
define CP_ACP = 0 # Win32 code page ANSI (current page for A-series APIs)
|
|
|
|
extern def WideCharToMultiByte(CodePage: UINT, dwFlags: DWORD, lpWideCharStr: WCHARPTR,
|
|
cchWideChar: int, lpMultiByteStr: CHARPTR, cbMultiByte: int,
|
|
lpDefaultChar: wb.LPCSTR, lpUsedDefaultChar: BOOL*) -> int
|
|
|
|
# Injected by App/main.vp (same MemBuddy instance as other modules)
|
|
_mbuddy: memhub.MemBuddy*
|
|
|
|
# Result outputs for ReadWholeFile / WriteFileAll (module globals)
|
|
LastReadSize: sizet = 0 # actual bytes read on success
|
|
LastReadErr: int = 0 # 1=open 2=size 3=empty 4=over limit 5=alloc failure
|
|
LastReadWinErr: ULONG = 0 # GetLastError() when open fails
|
|
LastWriteDone: sizet = 0 # bytes actually written by WriteFileAll
|
|
|
|
|
|
# ============================================================
|
|
# Generic byte strings
|
|
# ============================================================
|
|
|
|
# Copy Src to Dst incl. trailing NUL; returns 0 (no write-through) if capacity is insufficient
|
|
def CopyRaw(Dst: bytes, Cap: int, Src: str) -> int:
|
|
if Dst is None:
|
|
return 0
|
|
if Cap <= 0:
|
|
return 0
|
|
if Src is None:
|
|
Dst[0] = 0
|
|
return 0
|
|
n: sizet = string.strlen(Src)
|
|
if n + 1 > sizet(Cap):
|
|
Dst[0] = 0
|
|
return 0
|
|
for i in range(n):
|
|
Dst[i] = Src[i]
|
|
Dst[n] = 0
|
|
return 1
|
|
|
|
|
|
# Duplicate Src into the main pool (None on failure). Free via VFs._mbuddy.free
|
|
# (the same pool instance is shared across modules).
|
|
def DupStr(S: str) -> str:
|
|
if S is None:
|
|
return None
|
|
n: sizet = string.strlen(S)
|
|
b: bytes = _mbuddy.alloc(n + 1)
|
|
if b is None:
|
|
return None
|
|
for i in range(n):
|
|
b[i] = S[i]
|
|
b[n] = 0
|
|
return str(b)
|
|
|
|
|
|
# ASCII case-insensitive compare (Windows filenames are case-insensitive)
|
|
def StrEqNoCase(A: str, B: str) -> int:
|
|
i: int = 0
|
|
while True:
|
|
ca: int = int(A[i])
|
|
cb: int = int(B[i])
|
|
if ca >= 65 and ca <= 90:
|
|
ca += 32
|
|
if cb >= 65 and cb <= 90:
|
|
cb += 32
|
|
if ca != cb:
|
|
return 0
|
|
if ca == 0:
|
|
return 1
|
|
i += 1
|
|
|
|
|
|
# ============================================================
|
|
# Paths
|
|
# ============================================================
|
|
|
|
# Join Parent + "\" + Child into Out (capacity Cap); returns 1=ok, 0=overflow.
|
|
# Self-contained (no includes join) because here the capacity bound must be explicit.
|
|
def JoinPath(Out: bytes, Cap: int, Parent: str, Child: str) -> int:
|
|
if Out is None or Parent is None or Child is None:
|
|
return 0
|
|
pl: sizet = string.strlen(Parent)
|
|
cl: sizet = string.strlen(Child)
|
|
need_sep: int = 0
|
|
if pl > 0:
|
|
last: int = int(Parent[int(pl) - 1])
|
|
if last != 47 and last != 92: # '/' or '\'
|
|
need_sep = 1
|
|
total: sizet = pl + cl + sizet(need_sep)
|
|
if total + 1 > sizet(Cap):
|
|
return 0
|
|
i: sizet = 0
|
|
while i < pl:
|
|
Out[i] = Parent[i]
|
|
i += 1
|
|
if need_sep != 0:
|
|
Out[i] = '\\'
|
|
i += 1
|
|
for j in range(cl):
|
|
Out[i] = Child[j]
|
|
i += 1
|
|
Out[i] = 0
|
|
return 1
|
|
|
|
|
|
# Whether Path is an existing directory (attrs != INVALID and DIRECTORY bit set)
|
|
def PathIsDir(Path: str) -> int:
|
|
Attrs: ULONG = wf.GetFileAttributesA(Path)
|
|
if Attrs == INVALID_FILE_ATTRIBUTES:
|
|
return 0
|
|
if (Attrs & wf.FILE_ATTRIBUTE_DIRECTORY) != 0:
|
|
return 1
|
|
return 0
|
|
|
|
|
|
# Whether Path is an existing regular file (exists and not a directory)
|
|
def PathIsFile(Path: str) -> int:
|
|
Attrs: ULONG = wf.GetFileAttributesA(Path)
|
|
if Attrs == INVALID_FILE_ATTRIBUTES:
|
|
return 0
|
|
if (Attrs & wf.FILE_ATTRIBUTE_DIRECTORY) != 0:
|
|
return 0
|
|
return 1
|
|
|
|
|
|
# ============================================================
|
|
# Encoding conversion (A-series APIs use system ANSI; Viper strings are UTF-8)
|
|
# ============================================================
|
|
|
|
# UTF-8 -> ANSI (on failure/illegal UTF-8, copy as-is assuming already ANSI)
|
|
def Utf8ToAnsi(Src: str, Dst: bytes, Cap: int) -> int:
|
|
if Src is None:
|
|
Dst[0] = 0
|
|
return 0
|
|
n: int = wb.MultiByteToWideChar(CP_UTF8, 0, Src, -1, None, 0)
|
|
if n <= 0:
|
|
CopyRaw(Dst, Cap, Src)
|
|
return 0
|
|
W: WCHARPTR = stdlib.malloc(sizet(n) * 2)
|
|
if W is None:
|
|
CopyRaw(Dst, Cap, Src)
|
|
return 0
|
|
wb.MultiByteToWideChar(CP_UTF8, 0, Src, -1, W, n)
|
|
got: int = WideCharToMultiByte(CP_ACP, 0, W, -1, Dst, Cap, None, None)
|
|
stdlib.free(rawptr(W))
|
|
if got <= 0:
|
|
CopyRaw(Dst, Cap, Src)
|
|
return 0
|
|
return 1
|
|
|
|
|
|
# ANSI -> UTF-8 (copy as-is on failure)
|
|
def AnsiToUtf8(Src: str, Dst: bytes, Cap: int) -> int:
|
|
if Src is None:
|
|
Dst[0] = 0
|
|
return 0
|
|
n: int = wb.MultiByteToWideChar(CP_ACP, 0, Src, -1, None, 0)
|
|
if n <= 0:
|
|
CopyRaw(Dst, Cap, Src)
|
|
return 0
|
|
W: WCHARPTR = stdlib.malloc(sizet(n) * 2)
|
|
if W is None:
|
|
CopyRaw(Dst, Cap, Src)
|
|
return 0
|
|
wb.MultiByteToWideChar(CP_ACP, 0, Src, -1, W, n)
|
|
got: int = WideCharToMultiByte(CP_UTF8, 0, W, -1, Dst, Cap, None, None)
|
|
stdlib.free(rawptr(W))
|
|
if got <= 0:
|
|
CopyRaw(Dst, Cap, Src)
|
|
return 0
|
|
return 1
|
|
|
|
|
|
# Bytes needed for UTF-16 -> UTF-8 (incl. trailing NUL); <=0 means failure
|
|
def WideToUtf8Needed(Src: WCHARPTR) -> int:
|
|
if Src is None:
|
|
return 0
|
|
return WideCharToMultiByte(CP_UTF8, 0, Src, -1, None, 0, None, None)
|
|
|
|
|
|
# UTF-16 -> UTF-8 into Dst (capacity Cap); returns 1=ok
|
|
def WideToUtf8(Src: WCHARPTR, Dst: bytes, Cap: int) -> int:
|
|
if Src is None or Dst is None:
|
|
return 0
|
|
if WideCharToMultiByte(CP_UTF8, 0, Src, -1, Dst, Cap, None, None) <= 0:
|
|
return 0
|
|
return 1
|
|
|
|
|
|
# ============================================================
|
|
# File IO
|
|
# ============================================================
|
|
|
|
# Read until Want bytes from the current file pointer (looping); returns bytes read
|
|
def ReadFull(h: wb.HANDLE, Buf: bytes, Want: sizet) -> sizet:
|
|
total: sizet = 0
|
|
while total < Want:
|
|
chunk: sizet = Want - total
|
|
if chunk > 0x7FFFFFFF:
|
|
chunk = sizet(0x7FFFFFFF)
|
|
br: ULONG = 0
|
|
if wf.ReadFile(h, Buf + total, ULONG(chunk), &br, None) == 0:
|
|
break
|
|
if br == 0:
|
|
break
|
|
total += sizet(br)
|
|
return total
|
|
|
|
|
|
# Read a whole file into a stdlib buffer (NUL-terminated). Returns bytes (caller frees with
|
|
# stdlib.free) or None; see LastReadErr. Share is caller-provided (dwShareMode).
|
|
def ReadWholeFile(Path: str, MaxBytes: sizet, Share: ULONG) -> bytes:
|
|
global LastReadSize, LastReadErr, LastReadWinErr
|
|
LastReadSize = 0
|
|
LastReadErr = 0
|
|
LastReadWinErr = 0
|
|
H: wb.HANDLE = wf.CreateFileA(Path, wf.GENERIC_READ, Share,
|
|
None, wf.OPEN_EXISTING, wf.FILE_ATTRIBUTE_NORMAL, None)
|
|
if H == wb.INVALID_HANDLE_VALUE:
|
|
LastReadWinErr = wb.GetLastError()
|
|
LastReadErr = 1
|
|
return None
|
|
li: wb.LARGE_INTEGER = wb.LARGE_INTEGER()
|
|
if wf.GetFileSizeEx(H, &li) == 0:
|
|
wb.CloseHandle(H)
|
|
LastReadErr = 2
|
|
return None
|
|
Sz: sizet = sizet(li.QuadPart)
|
|
if Sz <= 0:
|
|
wb.CloseHandle(H)
|
|
LastReadErr = 3
|
|
return None
|
|
if MaxBytes > 0 and Sz > MaxBytes:
|
|
wb.CloseHandle(H)
|
|
LastReadErr = 4
|
|
return None
|
|
Buf: bytes = stdlib.malloc(Sz + 1)
|
|
if Buf is None:
|
|
wb.CloseHandle(H)
|
|
LastReadErr = 5
|
|
return None
|
|
Got: sizet = ReadFull(H, Buf, Sz)
|
|
wb.CloseHandle(H)
|
|
Buf[Got] = 0
|
|
LastReadSize = Got
|
|
return Buf
|
|
|
|
|
|
# Write Buf[0..Len) entirely to Path (CREATE_ALWAYS).
|
|
# Returns 1=complete, 0=open/write failure, -1=partial (see LastWriteDone)
|
|
def WriteFileAll(Path: str, Buf: bytes, Len: sizet) -> int:
|
|
global LastWriteDone
|
|
LastWriteDone = 0
|
|
if Buf is None:
|
|
return 0
|
|
H: wb.HANDLE = wf.CreateFileA(Path, wf.GENERIC_WRITE, wf.FILE_SHARE_READ,
|
|
None, wf.CREATE_ALWAYS, wf.FILE_ATTRIBUTE_NORMAL, None)
|
|
if H == wb.INVALID_HANDLE_VALUE:
|
|
return 0
|
|
total: sizet = 0
|
|
while total < Len:
|
|
chunk: sizet = Len - total
|
|
if chunk > 0x7FFFFFFF:
|
|
chunk = sizet(0x7FFFFFFF)
|
|
bw: ULONG = 0
|
|
if wf.WriteFile(H, Buf + total, ULONG(chunk), &bw, None) == 0:
|
|
break
|
|
if bw == 0:
|
|
break
|
|
total += sizet(bw)
|
|
wb.CloseHandle(H)
|
|
LastWriteDone = total
|
|
if total < Len:
|
|
return -1
|
|
return 1
|