Files
2026-10-17 21:57:25 +08:00

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