chore: commit G3.2+G3.3 deploy binaries + debug scripts; ignore debug run artifacts

This commit is contained in:
2026-08-16 10:27:36 +07:00
parent d0da12f42b
commit 700f612674
19 changed files with 2230 additions and 0 deletions
+175
View File
@@ -0,0 +1,175 @@
# Driver verify: SHM + spawn real bridge, reproduce user flow:
# load 6x Nexus -> open GUI ch=5 -> play notes -> switch instrument ch=5 (crash point)
# -> reopen GUI -> verify alive + writeIndex advancing + no crash.
import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
SHM_NAME = "SonicForge_DAW_IPC_VERIFY"
SIZE = 32768 # bigger than struct, safe
BRIDGE = r"C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe"
CDB = r"C:\Users\locpham\AppData\Local\Microsoft\WindowsApps\cdbX64.exe"
LOG = os.path.join(os.path.dirname(os.path.abspath(__file__)), "driver_cdb.log")
NEXUS = r"C:\Program Files\Common Files\VST3\Nexus.vst3"
AMPLE = r"C:\Program Files\Common Files\VST3\Ample Sound\ACDD.vst3"
kernel32 = ctypes.windll.kernel32
kernel32.CreateFileMappingA.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_char_p]
kernel32.CreateFileMappingA.restype = ctypes.c_void_p
kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t]
kernel32.MapViewOfFile.restype = ctypes.c_void_p
INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
PAGE_READWRITE = 0x04
FILE_MAP_ALL_ACCESS = 0xF001F
FILE_MAP_WRITE = 0x0002
FILE_MAP_READ = 0x0004
hMap = kernel32.CreateFileMappingA(INVALID_HANDLE_VALUE, None, PAGE_READWRITE, 0, SIZE, SHM_NAME.encode())
if not hMap:
print("CreateFileMapping failed", ctypes.get_last_error()); sys.exit(1)
ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0)
if not ptr:
print("MapViewOfFile failed", ctypes.get_last_error()); sys.exit(1)
def w32(off, fmt, *args):
ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt))
def write_control(type_, arg0, arg1, ch, path):
# find first free slot
for i in range(8):
off = 2836 + i * 1040
t = ctypes.c_uint32.from_address(ptr + off).value
if t == 0:
w32(off, 'IIII', type_, arg0, arg1, ch)
if path:
b = path.encode('utf-8')[:1023] + b'\x00'
ctypes.memmove(ptr + off + 16, b, len(b))
return i
raise RuntimeError("control queue full")
def submit_controls():
w32(11156, 'I', 0) # will set below
# reset type of all slots first
for i in range(8):
w32(2836 + i * 1040, 'I', 0)
def push_controls(events):
submit_controls()
n = 0
for (type_, arg0, arg1, ch, path) in events:
write_control(type_, arg0, arg1, ch, path)
n += 1
w32(11156, 'I', n)
def midi(cmd, ch, pitch, vel, data2=0, data3=0):
# bridge does not zero midiQueue after drain - always write slot 0, count 1
w32(2064, 'BBBBBB', cmd, ch, pitch, vel, data2, data3)
w32(2064 + 8, 'I', 0)
w32(2832, 'I', 1)
return True
def midi_count():
return ctypes.c_uint32.from_address(ptr + 2832).value
def write_index():
return ctypes.c_uint32.from_address(ptr + 4).value
def wait_log(pattern, timeout, since=0):
end = time.time() + timeout
lines = []
try:
with open(LOG, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
except FileNotFoundError:
pass
last = max(0, len(lines) - since)
while time.time() < end:
try:
with open(LOG, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
except FileNotFoundError:
lines = []
for i in range(last, len(lines)):
if pattern in lines[i]:
return lines[i].strip()
last = max(0, len(lines))
time.sleep(0.2)
return None
proc = subprocess.Popen(
[CDB, "-g", "-G", "-o", "-c", ".ecxr; .dump /ma C:\\Users\\locpham\\AppData\\Local\\Temp\\procdump\\dumps\\cdb_crash.dmp; !analyze -v; k L40; q", BRIDGE, "--shm", SHM_NAME],
env={**os.environ, "SF_SHM_NAME": SHM_NAME, "SF_PARENT_PID": str(os.getpid()),
"SF_SAMPLE_RATE": "48000", "SF_BLOCK_SIZE": "256"},
stdout=open(LOG, 'wb'), stderr=subprocess.STDOUT,
)
time.sleep(1.5)
print("bridge pid", proc.pid, "poll", proc.poll())
# 1. Load 6 channels Nexus
print("[1] load 6x Nexus...")
push_controls([(2, 0, 0, ch, NEXUS) for ch in range(6)])
print(" wait ch=5 loaded...")
r = wait_log("instrument loaded ch=5", 90)
print(" ", r)
# 2. Open GUI ch=5
print("[2] open GUI ch=5...")
push_controls([(4, 0, 0, 5, "nexus")])
r = wait_log("GUI attached", 30)
print(" ", r)
# 3. Play notes on ch=5 and ch=2 while editor open
print("[3] play notes...")
for k in range(40):
midi(0x9, 5, 60 + (k % 12), 100)
midi(0x9, 2, 48 + (k % 7), 90)
if k % 2 == 0:
midi(0x8, 5, 60 + ((k - 1) % 12), 0)
time.sleep(0.05)
# 4. Switch instrument ch=5 while GUI open (OLD CRASH POINT)
print("[4] switch instrument ch=5 -> Ample ACDD while GUI open...")
t0 = time.time()
push_controls([(2, 0, 0, 5, AMPLE)])
r = wait_log("instrument loaded ch=5", 40)
print(" ", r, "in", round(time.time() - t0, 1), "s")
alive = proc.poll() is None
wi = write_index()
time.sleep(2)
wi2 = write_index()
print(" alive:", alive, "writeIndex", wi, "->", wi2, "advancing:", wi2 > wi)
# 5. Reopen GUI ch=5 (after switch) - attach path again
print("[5] reopen GUI ch=5...")
push_controls([(4, 0, 0, 5, "ample")])
r = wait_log("GUI attached", 40)
print(" ", r)
# 6. Final liveness check + crash check
time.sleep(3)
alive = proc.poll() is None
wi3 = write_index()
time.sleep(2)
wi4 = write_index()
print("FINAL alive:", alive, "writeIndex", wi3, "->", wi4, "advancing:", wi4 > wi3)
if not alive:
print("!!! BRIDGE CRASHED exit", proc.returncode)
print("--- last 30 log lines ---")
try:
with open(LOG, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
for l in lines[-30:]:
print(l.rstrip())
except FileNotFoundError:
pass
# cleanup
kernel32.UnmapViewOfFile(ptr)
kernel32.CloseHandle(hMap)
try:
proc.terminate()
except Exception:
pass
time.sleep(0.5)
print("done (cdb)")
+175
View File
@@ -0,0 +1,175 @@
# Driver verify: SHM + spawn real bridge, reproduce user flow:
# load 6x Nexus -> open GUI ch=5 -> play notes -> switch instrument ch=5 (crash point)
# -> reopen GUI -> verify alive + writeIndex advancing + no crash.
import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
SHM_NAME = "SonicForge_DAW_IPC_VERIFY"
SIZE = 32768 # bigger than struct, safe
BRIDGE = r"C:\Users\locpham\SonicForgeStudio\src-tauri\binaries\daw_vst_bridge.exe"
CDB = r"C:\Users\locpham\AppData\Local\Microsoft\WindowsApps\cdbX64.exe"
LOG = os.path.join(os.path.dirname(os.path.abspath(__file__)), "driver_cdb_old.log")
NEXUS = r"C:\Program Files\Common Files\VST3\Nexus.vst3"
AMPLE = r"C:\Program Files\Common Files\VST3\Ample Sound\ACDD.vst3"
kernel32 = ctypes.windll.kernel32
kernel32.CreateFileMappingA.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_char_p]
kernel32.CreateFileMappingA.restype = ctypes.c_void_p
kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t]
kernel32.MapViewOfFile.restype = ctypes.c_void_p
INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
PAGE_READWRITE = 0x04
FILE_MAP_ALL_ACCESS = 0xF001F
FILE_MAP_WRITE = 0x0002
FILE_MAP_READ = 0x0004
hMap = kernel32.CreateFileMappingA(INVALID_HANDLE_VALUE, None, PAGE_READWRITE, 0, SIZE, SHM_NAME.encode())
if not hMap:
print("CreateFileMapping failed", ctypes.get_last_error()); sys.exit(1)
ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0)
if not ptr:
print("MapViewOfFile failed", ctypes.get_last_error()); sys.exit(1)
def w32(off, fmt, *args):
ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt))
def write_control(type_, arg0, arg1, ch, path):
# find first free slot
for i in range(8):
off = 2836 + i * 1040
t = ctypes.c_uint32.from_address(ptr + off).value
if t == 0:
w32(off, 'IIII', type_, arg0, arg1, ch)
if path:
b = path.encode('utf-8')[:1023] + b'\x00'
ctypes.memmove(ptr + off + 16, b, len(b))
return i
raise RuntimeError("control queue full")
def submit_controls():
w32(11156, 'I', 0) # will set below
# reset type of all slots first
for i in range(8):
w32(2836 + i * 1040, 'I', 0)
def push_controls(events):
submit_controls()
n = 0
for (type_, arg0, arg1, ch, path) in events:
write_control(type_, arg0, arg1, ch, path)
n += 1
w32(11156, 'I', n)
def midi(cmd, ch, pitch, vel, data2=0, data3=0):
# bridge does not zero midiQueue after drain - always write slot 0, count 1
w32(2064, 'BBBBBB', cmd, ch, pitch, vel, data2, data3)
w32(2064 + 8, 'I', 0)
w32(2832, 'I', 1)
return True
def midi_count():
return ctypes.c_uint32.from_address(ptr + 2832).value
def write_index():
return ctypes.c_uint32.from_address(ptr + 4).value
def wait_log(pattern, timeout, since=0):
end = time.time() + timeout
lines = []
try:
with open(LOG, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
except FileNotFoundError:
pass
last = max(0, len(lines) - since)
while time.time() < end:
try:
with open(LOG, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
except FileNotFoundError:
lines = []
for i in range(last, len(lines)):
if pattern in lines[i]:
return lines[i].strip()
last = max(0, len(lines))
time.sleep(0.2)
return None
proc = subprocess.Popen(
[CDB, "-g", "-G", "-o", "-c", ".ecxr; .dump /ma C:\\Users\\locpham\\AppData\\Local\\Temp\\procdump\\dumps\\cdb_crash.dmp; !analyze -v; k L40; q", BRIDGE, "--shm", SHM_NAME],
env={**os.environ, "SF_SHM_NAME": SHM_NAME, "SF_PARENT_PID": str(os.getpid()),
"SF_SAMPLE_RATE": "48000", "SF_BLOCK_SIZE": "256"},
stdout=open(LOG, 'wb'), stderr=subprocess.STDOUT,
)
time.sleep(1.5)
print("bridge pid", proc.pid, "poll", proc.poll())
# 1. Load 6 channels Nexus
print("[1] load 6x Nexus...")
push_controls([(2, 0, 0, ch, NEXUS) for ch in range(6)])
print(" wait ch=5 loaded...")
r = wait_log("instrument loaded ch=5", 90)
print(" ", r)
# 2. Open GUI ch=5
print("[2] open GUI ch=5...")
push_controls([(4, 0, 0, 5, "nexus")])
r = wait_log("GUI attached", 30)
print(" ", r)
# 3. Play notes on ch=5 and ch=2 while editor open
print("[3] play notes...")
for k in range(40):
midi(0x9, 5, 60 + (k % 12), 100)
midi(0x9, 2, 48 + (k % 7), 90)
if k % 2 == 0:
midi(0x8, 5, 60 + ((k - 1) % 12), 0)
time.sleep(0.05)
# 4. Switch instrument ch=5 while GUI open (OLD CRASH POINT)
print("[4] switch instrument ch=5 -> Ample ACDD while GUI open...")
t0 = time.time()
push_controls([(2, 0, 0, 5, AMPLE)])
r = wait_log("instrument loaded ch=5", 40)
print(" ", r, "in", round(time.time() - t0, 1), "s")
alive = proc.poll() is None
wi = write_index()
time.sleep(2)
wi2 = write_index()
print(" alive:", alive, "writeIndex", wi, "->", wi2, "advancing:", wi2 > wi)
# 5. Reopen GUI ch=5 (after switch) - attach path again
print("[5] reopen GUI ch=5...")
push_controls([(4, 0, 0, 5, "ample")])
r = wait_log("GUI attached", 40)
print(" ", r)
# 6. Final liveness check + crash check
time.sleep(3)
alive = proc.poll() is None
wi3 = write_index()
time.sleep(2)
wi4 = write_index()
print("FINAL alive:", alive, "writeIndex", wi3, "->", wi4, "advancing:", wi4 > wi3)
if not alive:
print("!!! BRIDGE CRASHED exit", proc.returncode)
print("--- last 30 log lines ---")
try:
with open(LOG, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
for l in lines[-30:]:
print(l.rstrip())
except FileNotFoundError:
pass
# cleanup
kernel32.UnmapViewOfFile(ptr)
kernel32.CloseHandle(hMap)
try:
proc.terminate()
except Exception:
pass
time.sleep(0.5)
print("done (cdb)")
+176
View File
@@ -0,0 +1,176 @@
# Driver verify: SHM + spawn real bridge, reproduce user flow:
# load 6x Nexus -> open GUI ch=5 -> play notes -> switch instrument ch=5 (crash point)
# -> reopen GUI -> verify alive + writeIndex advancing + no crash.
import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
SHM_NAME = "SonicForge_DAW_IPC_VERIFY"
SIZE = 32768 # bigger than struct, safe
BRIDGE = r"C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe"
PROCDUMP = r"C:\Users\locpham\AppData\Local\Temp\procdump\procdump64.exe"
DUMPDIR = r"C:\Users\locpham\AppData\Local\Temp\procdump\dumps"
LOG = os.path.join(os.path.dirname(os.path.abspath(__file__)), "driver_procdump.log")
NEXUS = r"C:\Program Files\Common Files\VST3\Nexus.vst3"
AMPLE = r"C:\Program Files\Common Files\VST3\Ample Sound\ACDD.vst3"
kernel32 = ctypes.windll.kernel32
kernel32.CreateFileMappingA.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_char_p]
kernel32.CreateFileMappingA.restype = ctypes.c_void_p
kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t]
kernel32.MapViewOfFile.restype = ctypes.c_void_p
INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
PAGE_READWRITE = 0x04
FILE_MAP_ALL_ACCESS = 0xF001F
FILE_MAP_WRITE = 0x0002
FILE_MAP_READ = 0x0004
hMap = kernel32.CreateFileMappingA(INVALID_HANDLE_VALUE, None, PAGE_READWRITE, 0, SIZE, SHM_NAME.encode())
if not hMap:
print("CreateFileMapping failed", ctypes.get_last_error()); sys.exit(1)
ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0)
if not ptr:
print("MapViewOfFile failed", ctypes.get_last_error()); sys.exit(1)
def w32(off, fmt, *args):
ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt))
def write_control(type_, arg0, arg1, ch, path):
# find first free slot
for i in range(8):
off = 2836 + i * 1040
t = ctypes.c_uint32.from_address(ptr + off).value
if t == 0:
w32(off, 'IIII', type_, arg0, arg1, ch)
if path:
b = path.encode('utf-8')[:1023] + b'\x00'
ctypes.memmove(ptr + off + 16, b, len(b))
return i
raise RuntimeError("control queue full")
def submit_controls():
w32(11156, 'I', 0) # will set below
# reset type of all slots first
for i in range(8):
w32(2836 + i * 1040, 'I', 0)
def push_controls(events):
submit_controls()
n = 0
for (type_, arg0, arg1, ch, path) in events:
write_control(type_, arg0, arg1, ch, path)
n += 1
w32(11156, 'I', n)
def midi(cmd, ch, pitch, vel, data2=0, data3=0):
# bridge does not zero midiQueue after drain - always write slot 0, count 1
w32(2064, 'BBBBBB', cmd, ch, pitch, vel, data2, data3)
w32(2064 + 8, 'I', 0)
w32(2832, 'I', 1)
return True
def midi_count():
return ctypes.c_uint32.from_address(ptr + 2832).value
def write_index():
return ctypes.c_uint32.from_address(ptr + 4).value
def wait_log(pattern, timeout, since=0):
end = time.time() + timeout
lines = []
try:
with open(LOG, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
except FileNotFoundError:
pass
last = max(0, len(lines) - since)
while time.time() < end:
try:
with open(LOG, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
except FileNotFoundError:
lines = []
for i in range(last, len(lines)):
if pattern in lines[i]:
return lines[i].strip()
last = max(0, len(lines))
time.sleep(0.2)
return None
proc = subprocess.Popen(
[PROCDUMP, "-accepteula", "-x", DUMPDIR, BRIDGE, "--shm", SHM_NAME], # second chance = real crash (first-chance caught a benign Nexus json throw)
env={**os.environ, "SF_SHM_NAME": SHM_NAME, "SF_PARENT_PID": str(os.getpid()),
"SF_SAMPLE_RATE": "48000", "SF_BLOCK_SIZE": "256"},
stdout=open(LOG, 'wb'), stderr=subprocess.STDOUT,
)
time.sleep(1.5)
print("bridge pid", proc.pid, "poll", proc.poll())
# 1. Load 6 channels Nexus
print("[1] load 6x Nexus...")
push_controls([(2, 0, 0, ch, NEXUS) for ch in range(6)])
print(" wait ch=5 loaded...")
r = wait_log("instrument loaded ch=5", 90)
print(" ", r)
# 2. Open GUI ch=5
print("[2] open GUI ch=5...")
push_controls([(4, 0, 0, 5, "nexus")])
r = wait_log("GUI attached", 30)
print(" ", r)
# 3. Play notes on ch=5 and ch=2 while editor open
print("[3] play notes...")
for k in range(40):
midi(0x9, 5, 60 + (k % 12), 100)
midi(0x9, 2, 48 + (k % 7), 90)
if k % 2 == 0:
midi(0x8, 5, 60 + ((k - 1) % 12), 0)
time.sleep(0.05)
# 4. Switch instrument ch=5 while GUI open (OLD CRASH POINT)
print("[4] switch instrument ch=5 -> Ample ACDD while GUI open...")
t0 = time.time()
push_controls([(2, 0, 0, 5, AMPLE)])
r = wait_log("instrument loaded ch=5", 40)
print(" ", r, "in", round(time.time() - t0, 1), "s")
alive = proc.poll() is None
wi = write_index()
time.sleep(2)
wi2 = write_index()
print(" alive:", alive, "writeIndex", wi, "->", wi2, "advancing:", wi2 > wi)
# 5. Reopen GUI ch=5 (after switch) - attach path again
print("[5] reopen GUI ch=5...")
push_controls([(4, 0, 0, 5, "ample")])
r = wait_log("GUI attached", 40)
print(" ", r)
# 6. Final liveness check + crash check
time.sleep(3)
alive = proc.poll() is None
wi3 = write_index()
time.sleep(2)
wi4 = write_index()
print("FINAL alive:", alive, "writeIndex", wi3, "->", wi4, "advancing:", wi4 > wi3)
if not alive:
print("!!! BRIDGE CRASHED exit", proc.returncode)
print("--- last 30 log lines ---")
try:
with open(LOG, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
for l in lines[-30:]:
print(l.rstrip())
except FileNotFoundError:
pass
# cleanup
kernel32.UnmapViewOfFile(ptr)
kernel32.CloseHandle(hMap)
try:
proc.terminate()
except Exception:
pass
time.sleep(0.5)
print("done (procdump)")
@@ -0,0 +1,47 @@
p = r"C:/Users/locpham/SonicForgeStudio/native_bridge/src/Vst3Instrument.cpp"
data = open(p, "rb").read()
n = b"\r\n"
old1 = n.join([
b" tresult queryInterface(const char* iid, void** v) override {",
b" if (std::memcmp(iid, Steinberg::Vst::IComponentHandler::iid, 16) == 0 ||",
b" std::memcmp(iid, Steinberg::FUnknown::iid, 16) == 0) {",
b" *v = static_cast<Steinberg::Vst::IComponentHandler*>(this);",
b" addRef();",
b" return kResultOk;",
b" }",
b" *v = nullptr;",
b" return kNoInterface;",
b" }",
])
new1 = n.join([
b" tresult queryInterface(const char*, void** v) override {",
b" *v = nullptr;",
b" return kNoInterface;",
b" }",
])
old2 = n.join([
b" tresult queryInterface(const char* iid, void** v) override {",
b" if (std::memcmp(iid, Steinberg::IPlugFrame::iid, 16) == 0 ||",
b" std::memcmp(iid, Steinberg::FUnknown::iid, 16) == 0) {",
b" *v = static_cast<Steinberg::IPlugFrame*>(this);",
b" addRef();",
b" return kResultOk;",
b" }",
b" *v = nullptr;",
b" return kNoInterface;",
b" }",
])
new2 = n.join([
b" tresult queryInterface(const char*, void** v) override {",
b" *v = nullptr;",
b" return kNoInterface;",
b" }",
])
c1 = data.count(old1)
c2 = data.count(old2)
data = data.replace(old1, new1).replace(old2, new2)
open(p, "wb").write(data)
print(f"edit1={c1} edit2={c2}")
+194
View File
@@ -0,0 +1,194 @@
import subprocess
def load(p):
return open(p, encoding='utf-8', newline='').read()
def save(p, s):
open(p, 'w', encoding='utf-8', newline='').write(s)
def rep(s, needle, repl, tag):
n = s.count(needle)
if n != 1:
raise SystemExit("FAIL %s: needle count=%d" % (tag, n))
return s.replace(needle, repl)
cr = '\r\n'
# ---- main.cpp ----
mp = load('native_bridge/src/main.cpp')
mp = rep(mp,
"int main(int argc, char* argv[]) {" + cr + " std::cout << \"[NativeBridge] Starting DAW Host Bridge Engine...\" << std::endl;" + cr + "#ifdef _WIN32" + cr + " CoInitializeEx(nullptr, COINIT_MULTITHREADED);" + cr + cr + " // Disable Power Throttling (Efficiency Mode) for this background process",
"int main(int argc, char* argv[]) {" + cr + " std::cout << \"[NativeBridge] Starting DAW Host Bridge Engine...\" << std::endl;" + cr + "#ifdef _WIN32" + cr + " // Disable Power Throttling (Efficiency Mode) for this background process",
"coinit")
mp = rep(mp,
" SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &powerState, sizeof(powerState));" + cr + cr + " // Elevate main audio processing thread to time-critical priority" + cr + " SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);",
" SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &powerState, sizeof(powerState));",
"timecritical")
mp = rep(mp,
" instruments.setEditorOpenPredicate([](const std::string& /*lp*/) {" + cr + " return false;" + cr + " });",
" instruments.setEditorOpenPredicate([](const std::string& lp) {" + cr + " std::lock_guard<std::mutex> lk(g_editorMutex);" + cr + " auto it = g_editorPathCount.find(lp);" + cr + " return it != g_editorPathCount.end() && it->second > 0;" + cr + " });",
"predicate")
blob = subprocess.check_output(['git', 'show', '0cec3d3:native_bridge/src/main.cpp']).decode('utf-8')
if '\r\n' not in blob and '\n' in blob:
blob = blob.replace('\n', '\r\n')
def fn_start(s):
i = s.find('static void post_close_gui(uint32_t ch, HWND hwnd) {' + cr + ' ChannelWorker* w = nullptr;')
if i < 0:
i = s.find('static void post_close_gui(uint32_t ch, HWND hwnd) {\n ChannelWorker* w = nullptr;')
if i < 0:
raise SystemExit("fn_start not found")
return i
cur_start = fn_start(mp)
cur_end = mp.find('int main(int argc, char* argv[]) {', cur_start)
old_start = fn_start(blob)
old_end = blob.find('int main(int argc, char* argv[]) {', old_start)
mp = rep(mp, mp[cur_start:cur_end], blob[old_start:old_end], "post_close_gui")
needle5 = (
" workers[guiCh]->post([&instruments, guiCh, arg1, arg2 = pluginId]() {" + cr +
" if (!instruments.get(guiCh)) {" + cr +
" std::cerr << \"[NativeBridge] GUI attach FAILED hwnd=\" << (void*)arg1" + cr +
" << \" plugin=\" << arg2 << \" ch=\" << guiCh << \" (no instrument loaded)\" << std::endl;" + cr +
" return;" + cr +
" }" + cr +
" if (auto* inst0 = instruments.get(guiCh)) {" + cr +
" if (inst0->hasAttachedView()) {" + cr +
" return;" + cr +
" }" + cr +
" }" + cr +
" void* hwnd = (void*)arg1;" + cr +
"#ifdef _WIN32" + cr +
" HWND nativeHwnd = nullptr;" + cr +
" {" + cr +
" std::lock_guard<std::mutex> lock(g_guiMutex);" + cr +
" auto it = g_guiWindows.find(guiCh);" + cr +
" if (it != g_guiWindows.end()) nativeHwnd = (HWND)it->second;" + cr +
" }" + cr +
" if (hwnd == 0) {" + cr +
" if (nativeHwnd && IsWindow(nativeHwnd)) {" + cr +
" hwnd = (void*)nativeHwnd;" + cr +
" SetWindowTextA(nativeHwnd, arg2.c_str());" + cr +
" ShowWindow(nativeHwnd, SW_SHOW);" + cr +
" SetForegroundWindow(nativeHwnd);" + cr +
" // Reuse: cap nhat USERDATA (channel+1) - inst CU da bi thay" + cr +
" // the boi assign() -> WM_DESTROY sau nay lookup inst MOI." + cr +
" SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));" + cr +
" } else {" + cr +
" nativeHwnd = (HWND)create_native_vst_window(arg2.c_str());" + cr +
" if (!nativeHwnd) {" + cr +
" std::cerr << \"[NativeBridge] GUI create window FAILED plugin=\" << arg2 << std::endl;" + cr +
" return;" + cr +
" }" + cr +
" {" + cr +
" std::lock_guard<std::mutex> lock(g_guiMutex);" + cr +
" g_guiWindows[guiCh] = nativeHwnd; // keep window alive" + cr +
" g_hwndToCh[nativeHwnd] = guiCh; // WM_DESTROY cleanup" + cr +
" }" + cr +
" SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));" + cr +
" hwnd = (void*)nativeHwnd;" + cr +
" }" + cr +
" }" + cr +
"#endif" + cr +
" if (auto* inst0 = instruments.get(guiCh)) {" + cr +
" if (inst0->hasAttachedView()) {" + cr +
" return;" + cr +
" }" + cr +
" }" + cr +
"#ifdef _WIN32" + cr +
" // Option B: chi 1 editor VST mo tai 1 thoi diem toan"
)
repl5 = (
" void* hwnd = (void*)arg1;" + cr +
"#ifdef _WIN32" + cr +
" // Window PHAI thuoc MAIN thread (audio loop pump nay dispatch" + cr +
" // messages cua no moi vong lap). Tao/cap nhat window ngay tai day." + cr +
" HWND nativeHwnd = nullptr;" + cr +
" {" + cr +
" std::lock_guard<std::mutex> lock(g_guiMutex);" + cr +
" auto it = g_guiWindows.find(guiCh);" + cr +
" if (it != g_guiWindows.end()) nativeHwnd = (HWND)it->second;" + cr +
" }" + cr +
" if (hwnd == 0) {" + cr +
" if (nativeHwnd && IsWindow(nativeHwnd)) {" + cr +
" hwnd = (void*)nativeHwnd;" + cr +
" SetWindowTextA(nativeHwnd, pluginId.c_str());" + cr +
" ShowWindow(nativeHwnd, SW_SHOW);" + cr +
" SetForegroundWindow(nativeHwnd);" + cr +
" // Reuse: cap nhat USERDATA (channel+1) - inst CU da bi thay" + cr +
" // the boi assign() -> WM_DESTROY sau nay lookup inst MOI." + cr +
" SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));" + cr +
" } else {" + cr +
" nativeHwnd = (HWND)create_native_vst_window(pluginId.c_str());" + cr +
" if (!nativeHwnd) {" + cr +
" std::cerr << \"[NativeBridge] GUI create window FAILED plugin=\" << pluginId << std::endl;" + cr +
" return;" + cr +
" }" + cr +
" {" + cr +
" std::lock_guard<std::mutex> lock(g_guiMutex);" + cr +
" g_guiWindows[guiCh] = nativeHwnd; // keep window alive" + cr +
" g_hwndToCh[nativeHwnd] = guiCh; // WM_DESTROY cleanup" + cr +
" }" + cr +
" SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));" + cr +
" hwnd = (void*)nativeHwnd;" + cr +
" }" + cr +
" }" + cr +
"#endif" + cr +
" workers[guiCh]->post([&instruments, guiCh, hwnd, arg2 = pluginId]() {" + cr +
" if (!instruments.get(guiCh)) {" + cr +
" std::cerr << \"[NativeBridge] GUI attach FAILED hwnd=\" << hwnd" + cr +
" << \" plugin=\" << arg2 << \" ch=\" << guiCh << \" (no instrument loaded)\" << std::endl;" + cr +
" return;" + cr +
" }" + cr +
" if (auto* inst0 = instruments.get(guiCh)) {" + cr +
" if (inst0->hasAttachedView()) {" + cr +
" return;" + cr +
" }" + cr +
" }" + cr +
"#ifdef _WIN32" + cr +
" // Option B: chi 1 editor VST mo tai 1 thoi diem toan"
)
mp = rep(mp, needle5, repl5, "handleOpenGui-window")
mp = rep(mp,
" try {" + cr + " instruments.setBypassed(true);" + cr + " sleep_ms(10);" + cr + " ok = inst->reloadForGUI();" + cr + " instruments.setBypassed(false);" + cr + " // IME recursion fix: strip the editor's IMC before",
" try {" + cr + " ok = inst->reloadForGUI();" + cr + " // IME recursion fix: strip the editor's IMC before",
"bypass-open1")
mp = rep(mp,
" } catch (...) {" + cr + " instruments.setBypassed(false);" + cr + " std::cerr << \"[NativeBridge] GUI attach EXCEPTION ch=\" << guiCh",
" } catch (...) {" + cr + " std::cerr << \"[NativeBridge] GUI attach EXCEPTION ch=\" << guiCh",
"bypass-open2")
mp = rep(mp,
' << " (channel active with editor open)" << std::endl;',
' << " (channel muted while editor open)" << std::endl;',
"logmsg")
mp = rep(mp,
" if (ch != UINT32_MAX && g_engine) {" + cr + " auto* inst = g_engine->get(ch);" + cr + " if (inst) {" + cr + " inst->resizeView(w, h);" + cr + " }" + cr + " }" + cr + " return 0;",
" if (ch != UINT32_MAX && g_workers) {" + cr + " auto wit = g_workers->find(ch);" + cr + " if (wit != g_workers->end()) {" + cr + " // onSize() must run on the channel worker (the view was attached" + cr + " // in its COM STA apartment), not on the main pump thread." + cr + " wit->second->post([ch, w, h]() {" + cr + " if (!g_engine) return;" + cr + " auto* i = g_engine->get(ch);" + cr + " if (i) i->resizeView(w, h);" + cr + " });" + cr + " }" + cr + " }" + cr + " return 0;",
"wmsize")
save('native_bridge/src/main.cpp', mp)
# ---- Vst3Instrument.cpp ----
vp = load('native_bridge/src/Vst3Instrument.cpp')
vp = rep(vp,
"bool Vst3Instrument::reloadForGUI() {" + cr + "#ifndef HAVE_VST3SDK" + cr + " return false;" + cr + "#else" + cr + " auto* s = static_cast<Vst3HostState*>(state_);" + cr + " if (!s || !s->controller) return false;" + cr + " return true;" + cr + "#endif" + cr + "}",
"bool Vst3Instrument::reloadForGUI() {" + cr + "#ifndef HAVE_VST3SDK" + cr + " return false;" + cr + "#else" + cr + " auto* s = static_cast<Vst3HostState*>(state_);" + cr + " if (!s || !s->controller) return false;" + cr + " if (s->view && guiAttached_) return true;" + cr + " if (hasAttachedOnce_ && !guiAttached_) {" + cr + " // Reopen GUI sau khi dong: mot so plugin (Nexus...) hang o" + cr + " // view->attached() LAN 2 tren cung component instance. Tao lai inst" + cr + " // moi tren CUNG worker thread (COM STA con song) roi attach lai." + cr + " // PHAN NAY PHAI chay tren worker thread — apartment cua channel song" + cr + " // o day; chay tren thread tam thi apartment moi chet ngay sau do" + cr + " // (reopen lan sau -> treo nhu bridge_like)." + cr + " if (!reload()) return false;" + cr + " s = static_cast<Vst3HostState*>(state_);" + cr + " if (!s || !s->controller) return false;" + cr + " }" + cr + " return true;" + cr + "#endif" + cr + "}",
"reloadForGUI")
vp = rep(vp,
"void Vst3Instrument::closeGUI() {" + cr + "#ifndef HAVE_VST3SDK" + cr + " return;" + cr + "#else" + cr + " auto* s = static_cast<Vst3HostState*>(state_);" + cr + " if (s && s->view) {" + cr + " if (guiAttached_) {" + cr + " try {" + cr + " s->view->removed();" + cr + " } catch (...) {" + cr + " std::cerr << \"[Vst3Instrument] Exception inside view->removed()\" << std::endl;" + cr + " }" + cr + " }" + cr + " s->view = nullptr;" + cr + " }" + cr + " guiAttached_ = false;" + cr + "#endif" + cr + "}",
"void Vst3Instrument::closeGUI() {" + cr + "#ifndef HAVE_VST3SDK" + cr + " return;" + cr + "#else" + cr + " auto* s = static_cast<Vst3HostState*>(state_);" + cr + " // Do NOT call s->view->removed(): Nexus's removed() enters an internal" + cr + " // modal wait for a message only this thread's message pump can dispatch" + cr + " // — but this thread is inside removed(), so it deadlocks forever" + cr + " // (observed: closeGUI job never returns; wdisp flood from the editor)." + cr + " // Nulling the view and letting the next terminate() (reload / destructor)" + cr + " // tear the editor windows down on this same thread is the deterministic" + cr + " // path out. Callers (post_close_gui / load job) additionally destroy the" + cr + " // editor's child windows right here on this thread so the plugin stops" + cr + " // pumping; the leaked editor object is inert once its windows are gone." + cr + " if (s) s->view = nullptr;" + cr + " guiAttached_ = false;" + cr + "#endif" + cr + "}",
"closeGUI")
save('native_bridge/src/Vst3Instrument.cpp', vp)
print("ALL EDITS OK")
+50
View File
@@ -0,0 +1,50 @@
import subprocess
def load(p):
return open(p, encoding='utf-8', newline='').read()
def save(p, s):
open(p, 'w', encoding='utf-8', newline='').write(s)
def rep(s, needle, repl, tag):
n = s.count(needle)
if n != 1:
raise SystemExit("FAIL %s: needle count=%d" % (tag, n))
return s.replace(needle, repl)
cr = '\r\n'
mp = load('native_bridge/src/main.cpp')
# 1. forward-declare helper near post_close_gui fwd decl
mp = rep(mp,
"static void post_close_gui(uint32_t ch, HWND hwnd); // defined after ChannelWorker",
"static void post_close_gui(uint32_t ch, HWND hwnd); // defined after ChannelWorker" + cr + "static void post_resize_view(uint32_t ch, int w, int h); // defined after ChannelWorker",
"fwd")
# 2. define helper after ChannelWorker class, before same-plugin helpers
anchor = "// --- same-plugin-DLL silence helpers"
helper = (
"// resizeView() must run on the channel worker (the view was attached in its" + cr +
"// COM STA apartment), never on the main pump thread." + cr +
"static void post_resize_view(uint32_t ch, int w, int h) {" + cr +
" if (g_workers) {" + cr +
" auto wit = g_workers->find(ch);" + cr +
" if (wit != g_workers->end()) {" + cr +
" wit->second->post([ch, w, h]() {" + cr +
" if (!g_engine) return;" + cr +
" auto* i = g_engine->get(ch);" + cr +
" if (i) i->resizeView(w, h);" + cr +
" });" + cr +
" }" + cr +
" }" + cr +
"}" + cr + cr + anchor)
mp = rep(mp, anchor, helper, "helper")
# 3. WM_SIZE: call helper instead of inline g_workers block
mp = rep(mp,
" if (ch != UINT32_MAX && g_workers) {" + cr + " auto wit = g_workers->find(ch);" + cr + " if (wit != g_workers->end()) {" + cr + " // onSize() must run on the channel worker (the view was attached" + cr + " // in its COM STA apartment), not on the main pump thread." + cr + " wit->second->post([ch, w, h]() {" + cr + " if (!g_engine) return;" + cr + " auto* i = g_engine->get(ch);" + cr + " if (i) i->resizeView(w, h);" + cr + " });" + cr + " }" + cr + " }" + cr + " return 0;",
" if (ch != UINT32_MAX) {" + cr + " post_resize_view(ch, w, h);" + cr + " }" + cr + " return 0;",
"wmsize")
save('native_bridge/src/main.cpp', mp)
print("OK")
+306
View File
@@ -0,0 +1,306 @@
#!/usr/bin/env python3
"""Apply owner-thread-destroy + pump-gate crash fix to main.cpp (LF file)."""
import sys, io
p = r"C:/Users/locpham/SonicForgeStudio/native_bridge/src/main.cpp"
s = open(p, "rb").read().decode("utf-8")
orig = s
def rep(old, new, tag):
global s
n = s.count(old)
assert n == 1, f"[{tag}] count={n}"
s = s.replace(old, new, 1)
print(f"ok: {tag}")
# 1. globals
rep(
"""static std::map<uint32_t, std::unique_ptr<ChannelWorker>>* g_workers = nullptr;""",
"""static std::map<uint32_t, std::unique_ptr<ChannelWorker>>* g_workers = nullptr;
static std::mutex g_tidMutex;
static std::map<DWORD, ChannelWorker*> g_tidToWorker; // worker tid -> worker (owner-thread destroy)""",
"globals")
# 2. fwd decl
rep(
"""static void post_close_gui(uint32_t ch, HWND hwnd); // defined after ChannelWorker""",
"""static void post_close_gui(uint32_t ch, HWND hwnd); // defined after ChannelWorker
static bool is_teardown_window(HWND hwnd); // fwd — defined after ChannelWorker""",
"fwd")
# 3. constructor register tid
rep(
""" th_ = std::thread([this] {
#ifdef _WIN32
OleInitialize(nullptr);""",
""" th_ = std::thread([this] {
#ifdef _WIN32
{
std::lock_guard<std::mutex> lk(g_tidMutex);
g_tidToWorker[GetCurrentThreadId()] = this;
}
OleInitialize(nullptr);""",
"register tid")
# 4. pump gate
rep(
""" for (int pumped = 0; pumped < 16; ++pumped) {
if (!PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) break;
TranslateMessage(&msg);""",
""" for (int pumped = 0; pumped < 16; ++pumped) {
if (!PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) break;
// CRASH FIX (0xc000041d): JUCE editor child windows of a
// plugin DLL are owned by the FIRST worker that ran the
// DLL's global JUCE MessageManager - not by the channel
// worker doing the teardown. While another worker's LOAD /
// close job tears an instance down (reloading / close in
// flight), dispatching a message (e.g. WM_PAINT) into the
// plugin wndproc reads instance state being destroyed ->
// AV in USER32 (observed: crash on the owner worker's
// pump, right after the teardown job closed the view).
// Drop the message instead (PeekMessageW already removed
// it): the editor may glitch, the bridge survives.
if (is_teardown_window(msg.hwnd)) continue;
TranslateMessage(&msg);""",
"pump gate")
# 5. unregister tid
rep(
"""#ifdef _WIN32
OleUninitialize();
#endif
});""",
"""#ifdef _WIN32
{
std::lock_guard<std::mutex> lk(g_tidMutex);
g_tidToWorker.erase(GetCurrentThreadId());
}
OleUninitialize();
#endif
});""",
"unregister tid")
# 6. post_and_wait
rep(
""" void post(std::function<void()> job) {
{
std::lock_guard<std::mutex> lk(mu_);
jobs_.push_back(std::move(job));
}
cv_.notify_all();
}
private:""",
""" void post(std::function<void()> job) {
{
std::lock_guard<std::mutex> lk(mu_);
jobs_.push_back(std::move(job));
}
cv_.notify_all();
}
// Run job on THIS worker and wait (5s cap) until it finished. Used to
// serialize a window destroy with the owner worker's pump. MUST NOT be
// called from a job running on this same worker (deadlock) - callers
// destroy directly when owner tid == current tid.
bool post_and_wait(std::function<void()> job) {
std::mutex doneMx;
std::condition_variable doneCv;
bool done = false;
{
std::lock_guard<std::mutex> lk(mu_);
jobs_.push_back([&]() {
job();
{
std::lock_guard<std::mutex> dk(doneMx);
done = true;
}
doneCv.notify_all();
});
}
cv_.notify_all();
std::unique_lock<std::mutex> dk(doneMx);
return doneCv.wait_for(dk, std::chrono::seconds(5), [&] { return done; });
}
private:""",
"post_and_wait")
# 7. helpers after close_in_flight
rep(
"""static bool close_in_flight(uint32_t ch) {
std::lock_guard<std::mutex> lk(g_attachMutex);
return g_closeInFlight[ch];
}""",
"""static bool close_in_flight(uint32_t ch) {
std::lock_guard<std::mutex> lk(g_attachMutex);
return g_closeInFlight[ch];
}
// True if hwnd (or any ancestor) is a plugin editor window whose channel is
// mid-teardown (reloading or close job in flight). Editor child windows
// (JUCE) have the native VST window as parent; the native window is in
// g_hwndToCh. JUCE's global message window has no such parent - walk stops
// -> false (never dropped).
static bool is_teardown_window(HWND hwnd) {
for (HWND h = hwnd; h; h = GetParent(h)) {
uint32_t ch = UINT32_MAX;
{
std::lock_guard<std::mutex> lock(g_guiMutex);
auto it = g_hwndToCh.find(h);
if (it != g_hwndToCh.end()) ch = it->second;
}
if (ch != UINT32_MAX) {
if (close_in_flight(ch)) return true;
return g_engine ? g_engine->isReloading(ch) : false;
}
}
return false;
}
// CRASH FIX (0xc000041d): plugin editor child windows are owned by the
// thread that first ran the plugin's JUCE MessageManager (a single worker),
// NOT by the channel worker doing the teardown. Cross-thread DestroyWindow
// races the owner's message pump. Post the destroy to the OWNER worker
// (serialized with its pump - no wndproc entry can race) and wait. Fall
// back to direct destroy when the owner is the current thread or unknown
// (leak > crash). Non-trivial: destroy jobs left queued on timeout are
// harmless - IsWindow guards them, and the pump gate already stopped
// dispatching to teardown windows.
static void destroy_window_on_owner(HWND hwnd) {
if (!hwnd || !IsWindow(hwnd)) return;
DWORD ownerTid = GetWindowThreadProcessId(hwnd, nullptr);
DWORD curTid = GetCurrentThreadId();
if (ownerTid == 0 || ownerTid == curTid) {
if (IsWindow(hwnd)) DestroyWindow(hwnd);
return;
}
ChannelWorker* owner = nullptr;
{
std::lock_guard<std::mutex> lk(g_tidMutex);
auto it = g_tidToWorker.find(ownerTid);
if (it != g_tidToWorker.end()) owner = it->second;
}
if (!owner) {
// Window belongs to a thread we don't control (e.g. main thread).
// Cross-thread DestroyWindow is unsafe -> leave it (leak > crash).
std::cerr << "[NativeBridge] destroy_window_on_owner: owner tid=" << ownerTid
<< " not a bridge worker - leaving window " << hwnd << std::endl;
return;
}
if (!owner->post_and_wait([hwnd]() {
if (IsWindow(hwnd)) DestroyWindow(hwnd);
}))
std::cerr << "[NativeBridge] destroy_window_on_owner: timeout waiting owner tid="
<< ownerTid << " to destroy " << hwnd << std::endl;
}""",
"helpers")
# 8. post_close_gui: destroy outside g_loadMutex, via owner
rep(
""" bool reloadOk = false;
{
// destroy children + reload in ONE lock: destroying the
// editor windows runs the plugin's window proc (DLL
// entry) on this worker; it must not race another
// thread's createInstance/createView of the same DLL.
// Same-thread destroy is valid (attachView created them
// on this worker); without destroy the editor keeps
// pumping (0x47b flood) while the audio loop processes
// the instance (2 threads in one DLL) -> Nexus USER32 crash.
std::lock_guard<std::mutex> lg(g_loadMutex);
// CRASH FIX: reload() = terminate + loadPlugin
// (createInstance) — Nexus may throw a C++ exception
// here too; catch it, treat as failed reload, and let
// the restore below unmute the channel (instance may be
// broken; next load/assign rebuilds it).
try {
#ifdef _WIN32
disable_ime_contexts(hwnd);
if (hwnd && IsWindow(hwnd)) {
while (HWND c = FindWindowExA(hwnd, nullptr, nullptr, nullptr)) {
std::cerr << "[dbg] closeGUI destroy child=" << c
<< " owner_tid=" << GetWindowThreadProcessId(c, nullptr)
<< " cur_tid=" << GetCurrentThreadId() << std::endl;
DestroyWindow(c);
}
}
#endif
reloadOk = i->reload();
} catch (...) {
std::cerr << "[NativeBridge] closeGUI reload EXCEPTION ch=" << ch
<< " — plugin threw (createInstance)" << std::endl;
reloadOk = false;
}
}""",
""" bool reloadOk = false;
#ifdef _WIN32
// CRASH FIX (0xc000041d): destroy the editor's child windows
// on the thread that OWNS them (the plugin's JUCE
// MessageManager thread - a single worker), never
// cross-thread. Wait for each destroy to finish (owner worker
// serializes it with its pump) BEFORE reload() - otherwise
// reload's createInstance enters the DLL while the owner
// thread is still inside the plugin wndproc (2 threads in one
// DLL -> Nexus crash). Kept OUTSIDE g_loadMutex: the wait
// must not block other loads (the owner worker may itself be
// waiting on that lock).
disable_ime_contexts(hwnd);
if (hwnd && IsWindow(hwnd)) {
while (HWND c = FindWindowExA(hwnd, nullptr, nullptr, nullptr)) {
std::cerr << "[dbg] closeGUI destroy child=" << c
<< " owner_tid=" << GetWindowThreadProcessId(c, nullptr)
<< " cur_tid=" << GetCurrentThreadId() << std::endl;
destroy_window_on_owner(c);
}
}
#endif
{
// reload() = terminate + loadPlugin (createInstance) —
// serialized with all other plugin-DLL entry points.
// Nexus may throw a C++ exception here too; catch it,
// treat as failed reload, and let the restore below
// unmute the channel (instance may be broken; next
// load/assign rebuilds it).
std::lock_guard<std::mutex> lg(g_loadMutex);
try {
reloadOk = i->reload();
} catch (...) {
std::cerr << "[NativeBridge] closeGUI reload EXCEPTION ch=" << ch
<< " — plugin threw (createInstance)" << std::endl;
reloadOk = false;
}
}""",
"post_close_gui destroy")
# 9. LOAD close-editor destroy loop via owner
rep(
""" // Destroy the old editor's child windows (worker-owned,
// created by attachView here) so they stop pumping;
// the parent window is hidden and kept for reuse.
// See post_close_gui for the same pattern.
while (HWND c = FindWindowExA(hToHide, nullptr, nullptr, nullptr)) {
std::cerr << "[dbg] load close-editor destroy child=" << c
<< " owner_tid=" << GetWindowThreadProcessId(c, nullptr)
<< " cur_tid=" << GetCurrentThreadId() << std::endl;
DestroyWindow(c);
}""",
""" // CRASH FIX (0xc000041d): destroy the old
// editor's child windows on the thread that OWNS
// them (JUCE MessageManager worker), never
// cross-thread - DestroyWindow from this LOAD
// worker raced the owner's pump dispatching
// WM_PAINT into the plugin wndproc -> AV. Wait
// for each destroy before assign() below. The
// parent window is hidden and kept for reuse.
// See post_close_gui for the same pattern.
while (HWND c = FindWindowExA(hToHide, nullptr, nullptr, nullptr)) {
std::cerr << "[dbg] load close-editor destroy child=" << c
<< " owner_tid=" << GetWindowThreadProcessId(c, nullptr)
<< " cur_tid=" << GetCurrentThreadId() << std::endl;
destroy_window_on_owner(c);
}""",
"load destroy loop")
assert s != orig
open(p, "wb").write(s.encode("utf-8"))
print("written")
+108
View File
@@ -0,0 +1,108 @@
# Drive the REAL app's bridge SHM: open GUI ch=0, screenshot, pixel analysis.
import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time, re
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
SHM_NAME = "SonicForge_DAW_IPC"
SIZE = 32768
LOG = os.path.join(os.path.dirname(os.path.abspath(__file__)), "probe_app.log")
user32 = ctypes.windll.user32
kernel32 = ctypes.windll.kernel32
kernel32.CreateFileMappingA.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_char_p]
kernel32.CreateFileMappingA.restype = ctypes.c_void_p
kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t]
kernel32.MapViewOfFile.restype = ctypes.c_void_p
INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
PAGE_READWRITE = 0x04
FILE_MAP_ALL_ACCESS = 0xF001F
hMap = kernel32.CreateFileMappingA(INVALID_HANDLE_VALUE, None, PAGE_READWRITE, 0, SIZE, SHM_NAME.encode())
ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0)
def w32(off, fmt, *args):
ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt))
def write_control(type_, arg0, arg1, ch, path):
for i in range(8):
off = 2836 + i * 1040
t = ctypes.c_uint32.from_address(ptr + off).value
if t == 0:
w32(off, 'IIII', type_, arg0, arg1, ch)
if path:
b = path.encode('utf-8')[:1023] + b'\x00'
ctypes.memmove(ptr + off + 16, b, len(b))
return i
raise RuntimeError("control queue full")
def push_controls(events):
for i in range(8):
w32(2836 + i * 1040, 'I', 0)
for (type_, arg0, arg1, ch, path) in events:
write_control(type_, arg0, arg1, ch, path)
w32(11156, 'I', len(events))
def wait_log(pattern, timeout):
t0 = time.time()
while time.time() - t0 < timeout:
try:
with open(LOG, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
for i in range(len(lines)):
if pattern in lines[i]:
return lines[i].strip()
except FileNotFoundError:
pass
time.sleep(0.2)
return None
# copy app bridge.log to local log for wait_log
applog = os.path.join(os.environ.get('APPDATA', '.'), 'SonicForgeDAW', 'logs', 'bridge.log')
open(LOG, 'w', encoding='utf-8').write(open(applog, encoding='utf-8', errors='replace').read())
print("push OPEN_GUI ch=0...")
push_controls([(4, 0, 0, 0, "nexus")])
r = wait_log("GUI attached", 40)
print("[open]", r)
hwnd = None
if r:
m = re.search(r'hwnd=([0-9A-Fa-f]+)', r)
if m:
hwnd = int(m.group(1), 16)
print("hwnd =", hex(hwnd) if hwnd else None)
time.sleep(6)
if hwnd:
rw = w.RECT()
user32.GetWindowRect(hwnd, ctypes.byref(rw))
print("window rect", rw.left, rw.top, rw.right, rw.bottom, "vis", user32.IsWindowVisible(hwnd))
wdt, hgt = rw.right - rw.left, rw.bottom - rw.top
ps = f"""
Add-Type -AssemblyName System.Drawing
$bmp = New-Object System.Drawing.Bitmap({wdt}, {hgt})
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.CopyFromScreen({rw.left}, {rw.top}, 0, 0, $bmp.Size)
$bmp.Save('C:/Users/locpham/SonicForgeStudio/native_bridge/debug/probe_app.png')
$g.Dispose(); $bmp.Dispose()
"""
subprocess.run(["powershell", "-NoProfile", "-Command", ps], check=False)
print("screenshot saved probe_app.png")
else:
# dump all windows with class SonicForge_Native_VST3_Class
import ctypes
found = []
@ctypes.WINFUNCTYPE(w.BOOL, w.HWND, w.LPARAM)
def cb(h, lp):
buf = ctypes.create_unicode_buffer(64)
user32.GetClassNameW(h, buf, 64)
if 'SonicForge' in buf.value:
found.append((h, buf.value))
return True
user32.EnumWindows(cb, 0)
print("native vst windows:", [(hex(h), c) for h, c in found])
kernel32.UnmapViewOfFile.argtypes = [ctypes.c_void_p]
kernel32.CloseHandle.argtypes = [ctypes.c_void_p]
kernel32.UnmapViewOfFile(ptr)
kernel32.CloseHandle(hMap)
print("done")
+130
View File
@@ -0,0 +1,130 @@
# Drive REAL app bridge SHM (SonicForge_DAW_IPC_<daw_pid>): load Nexus ch=0, open GUI, screenshot, brightness.
import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time, re
from PIL import Image
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
DAW_PID = sys.argv[1] if len(sys.argv) > 1 else "21148"
SHM_NAME = b"SonicForge_DAW_IPC"
SIZE = 32768
HERE = os.path.dirname(os.path.abspath(__file__))
LOG = os.path.join(HERE, "probe_app2.log")
APP_LOG = os.path.join(os.environ.get('APPDATA', '.'), 'SonicForgeDAW', 'logs', 'bridge.log')
NEXUS = r"C:\Program Files\Common Files\VST3\Nexus.vst3"
PNG = os.path.join(HERE, "probe_app2.png")
user32 = ctypes.windll.user32
kernel32 = ctypes.windll.kernel32
kernel32.OpenFileMappingA.argtypes = [ctypes.wintypes.DWORD, w.BOOL, ctypes.c_char_p]
kernel32.OpenFileMappingA.restype = ctypes.c_void_p
kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t]
kernel32.MapViewOfFile.restype = ctypes.c_void_p
FILE_MAP_ALL_ACCESS = 0xF001F
hMap = kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, False, SHM_NAME)
if not hMap:
print("OpenFileMapping failed", ctypes.get_last_error()); sys.exit(1)
ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0)
if not ptr:
print("MapViewOfFile failed", ctypes.get_last_error()); sys.exit(1)
def w32(off, fmt, *args):
ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt))
def write_control(type_, arg0, arg1, ch, path):
for i in range(8):
off = 2836 + i * 1040
t = ctypes.c_uint32.from_address(ptr + off).value
if t == 0:
w32(off, 'IIII', type_, arg0, arg1, ch)
if path:
b = path.encode('utf-8')[:1023] + b'\x00'
ctypes.memmove(ptr + off + 16, b, len(b))
return i
raise RuntimeError("control queue full")
def push_controls(events):
for i in range(8):
w32(2836 + i * 1040, 'I', 0)
for (type_, arg0, arg1, ch, path) in events:
write_control(type_, arg0, arg1, ch, path)
w32(11156, 'I', len(events))
def log_lines():
try:
with open(APP_LOG, 'r', encoding='utf-8', errors='replace') as f:
return f.readlines()
except FileNotFoundError:
return []
def wait_log(pattern, timeout, since):
end = time.time() + timeout
while time.time() < end:
lines = log_lines()
for i in range(since, len(lines)):
if pattern in lines[i]:
return lines[i].strip()
time.sleep(0.2)
return None
since = len(log_lines())
print("shm", SHM_NAME, "log lines", since)
print("[1] load Nexus ch=0...")
push_controls([(2, 0, 0, 0, NEXUS)])
r = wait_log("instrument loaded ch=0", 60, since)
print(" ", r)
since = len(log_lines())
print("[2] open GUI ch=0...")
push_controls([(4, 0, 0, 0, "nexus")])
r = wait_log("GUI attached", 40, since)
print("[open]", r)
hwnd = None
if r:
m = re.search(r'hwnd=([0-9A-Fa-f]+)', r)
if m:
hwnd = int(m.group(1), 16)
print("hwnd =", hex(hwnd) if hwnd else None)
time.sleep(8)
if hwnd:
rw = w.RECT()
user32.GetWindowRect(hwnd, ctypes.byref(rw))
print("window rect", rw.left, rw.top, rw.right, rw.bottom, "vis", user32.IsWindowVisible(hwnd))
wdt, hgt = rw.right - rw.left, rw.bottom - rw.top
if wdt <= 0 or hgt <= 0:
print("bad rect"); sys.exit(1)
ps = f"""
Add-Type -AssemblyName System.Drawing
$bmp = New-Object System.Drawing.Bitmap({wdt}, {hgt})
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.CopyFromScreen({rw.left}, {rw.top}, 0, 0, $bmp.Size)
$bmp.Save('{PNG}')
$g.Dispose(); $bmp.Dispose()
"""
subprocess.run(["powershell", "-NoProfile", "-Command", ps], check=False)
img = Image.open(PNG).convert('L')
px = list(img.getdata())
n = len(px)
avg = sum(px) / n
buckets = [0, 0, 0, 0, 0, 0, 0, 0]
for v in px:
buckets[v // 32] += 1
pct = [round(100 * b / n) for b in buckets]
print("screenshot", PNG, img.size, "avgLum", round(avg, 1), "buckets[0-31..224-255]", pct)
else:
found = []
@ctypes.WINFUNCTYPE(w.BOOL, w.HWND, w.LPARAM)
def cb(h, lp):
buf = ctypes.create_unicode_buffer(64)
user32.GetClassNameW(h, buf, 64)
if 'SonicForge' in buf.value:
found.append((h, buf.value))
return True
user32.EnumWindows(cb, 0)
print("native vst windows:", [(hex(h), c) for h, c in found])
kernel32.UnmapViewOfFile.argtypes = [ctypes.c_void_p]
kernel32.CloseHandle.argtypes = [ctypes.c_void_p]
kernel32.UnmapViewOfFile(ptr)
kernel32.CloseHandle(hMap)
print("done")
+121
View File
@@ -0,0 +1,121 @@
# Drive REAL app bridge SHM: cycle several VST3 plugins on ch=0, screenshot each, brightness.
# Usage: python probe_app3.py
import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time, re
from PIL import Image
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
SHM_NAME = b"SonicForge_DAW_IPC"
SIZE = 32768
HERE = os.path.dirname(os.path.abspath(__file__))
APP_LOG = os.path.join(os.environ.get('APPDATA', '.'), 'SonicForgeDAW', 'logs', 'bridge.log')
VST = r"C:\Program Files\Common Files\VST3"
PLUGINS = [
("nexus", os.path.join(VST, "Nexus.vst3")),
("kontakt", os.path.join(VST, "Kontakt.vst3")),
("scaler2", os.path.join(VST, "Scaler2.vst3")),
("ample", os.path.join(VST, "Ample Sound", "ACDD.vst3")),
]
user32 = ctypes.windll.user32
kernel32 = ctypes.windll.kernel32
kernel32.OpenFileMappingA.argtypes = [ctypes.wintypes.DWORD, w.BOOL, ctypes.c_char_p]
kernel32.OpenFileMappingA.restype = ctypes.c_void_p
kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t]
kernel32.MapViewOfFile.restype = ctypes.c_void_p
FILE_MAP_ALL_ACCESS = 0xF001F
hMap = kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, False, SHM_NAME)
if not hMap:
print("OpenFileMapping failed", ctypes.get_last_error()); sys.exit(1)
ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0)
if not ptr:
print("MapViewOfFile failed", ctypes.get_last_error()); sys.exit(1)
def w32(off, fmt, *args):
ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt))
def write_control(type_, arg0, arg1, ch, path):
for i in range(8):
off = 2836 + i * 1040
t = ctypes.c_uint32.from_address(ptr + off).value
if t == 0:
w32(off, 'IIII', type_, arg0, arg1, ch)
if path:
b = path.encode('utf-8')[:1023] + b'\x00'
ctypes.memmove(ptr + off + 16, b, len(b))
return i
raise RuntimeError("control queue full")
def push_controls(events):
for i in range(8):
w32(2836 + i * 1040, 'I', 0)
for (type_, arg0, arg1, ch, path) in events:
write_control(type_, arg0, arg1, ch, path)
w32(11156, 'I', len(events))
def log_lines():
try:
with open(APP_LOG, 'r', encoding='utf-8', errors='replace') as f:
return f.readlines()
except FileNotFoundError:
return []
def wait_log(pattern, timeout, since):
end = time.time() + timeout
while time.time() < end:
lines = log_lines()
for i in range(since, len(lines)):
if pattern in lines[i]:
return lines[i].strip()
time.sleep(0.2)
return None
def shot(hwnd, tag):
rw = w.RECT()
user32.GetWindowRect(hwnd, ctypes.byref(rw))
wdt, hgt = rw.right - rw.left, rw.bottom - rw.top
if wdt <= 0 or hgt <= 0:
return None
png = os.path.join(HERE, f"probe_app3_{tag}.png")
ps = f"""
Add-Type -AssemblyName System.Drawing
$bmp = New-Object System.Drawing.Bitmap({wdt}, {hgt})
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.CopyFromScreen({rw.left}, {rw.top}, 0, 0, $bmp.Size)
$bmp.Save('{png}')
$g.Dispose(); $bmp.Dispose()
"""
subprocess.run(["powershell", "-NoProfile", "-Command", ps], check=False)
img = Image.open(png).convert('L')
px = list(img.getdata())
n = len(px)
avg = sum(px) / n
buckets = [0, 0, 0, 0, 0, 0, 0, 0]
for v in px:
buckets[v // 32] += 1
pct = [round(100 * b / n) for b in buckets]
return f"avgLum {round(avg,1)} buckets[0-31..224-255] {pct} size {img.size}"
for tag, path in PLUGINS:
since = len(log_lines())
print(f"--- {tag} ---")
push_controls([(2, 0, 0, 0, path)])
r = wait_log("instrument loaded ch=0", 90, since)
print(" load:", (r or "TIMEOUT").split(" type=")[0])
since = len(log_lines())
push_controls([(4, 0, 0, 0, tag)])
r = wait_log("GUI attached", 40, since)
print(" open:", (r or "TIMEOUT")[:100])
m = re.search(r'hwnd=([0-9A-Fa-f]+)', r or "")
if not m:
print(" NO HWND"); continue
hwnd = int(m.group(1), 16)
time.sleep(8)
res = shot(hwnd, tag)
print(" shot:", res)
# close GUI via WM_CLOSE (VstWindowProc hides + detaches view)
user32.SendMessageW(hwnd, 0x0010, 0, 0) # WM_CLOSE
time.sleep(3)
print("done")
+180
View File
@@ -0,0 +1,180 @@
# Verify fix flow user: create SHM plain -> spawn bridge (no debugger) -> load Nexus ch=0
# -> open GUI -> screenshot -> close -> reopen -> verify unmute + other plugin loads.
import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time, re
from PIL import Image
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
SHM_NAME = "SonicForge_DAW_IPC"
SIZE = 32768
HERE = os.path.dirname(os.path.abspath(__file__))
BRIDGE = r"C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe"
LOG = os.path.join(HERE, "probe_fix_flow.log")
NEXUS = r"C:\Program Files\Common Files\VST3\Nexus.vst3"
AMPLE = r"C:\Program Files\Common Files\VST3\Ample Sound\ACDD.vst3"
kernel32 = ctypes.windll.kernel32
kernel32.CreateFileMappingA.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_char_p]
kernel32.CreateFileMappingA.restype = ctypes.c_void_p
kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t]
kernel32.MapViewOfFile.restype = ctypes.c_void_p
INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
PAGE_READWRITE = 0x04
FILE_MAP_ALL_ACCESS = 0xF001F
hMap = kernel32.CreateFileMappingA(INVALID_HANDLE_VALUE, None, PAGE_READWRITE, 0, SIZE, SHM_NAME.encode())
if not hMap:
print("CreateFileMapping failed", ctypes.get_last_error()); sys.exit(1)
ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0)
if not ptr:
print("MapViewOfFile failed", ctypes.get_last_error()); sys.exit(1)
def w32(off, fmt, *args):
ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt))
def write_control(type_, arg0, arg1, ch, path):
for i in range(8):
off = 2836 + i * 1040
t = ctypes.c_uint32.from_address(ptr + off).value
if t == 0:
w32(off, 'IIII', type_, arg0, arg1, ch)
if path:
b = path.encode('utf-8')[:1023] + b'\x00'
ctypes.memmove(ptr + off + 16, b, len(b))
return i
raise RuntimeError("control queue full")
def push_controls(events):
for i in range(8):
w32(2836 + i * 1040, 'I', 0)
for (type_, arg0, arg1, ch, path) in events:
write_control(type_, arg0, arg1, ch, path)
w32(11156, 'I', len(events))
def write_index():
return ctypes.c_uint32.from_address(ptr + 4).value
def wait_log(pattern, timeout, since=0):
end = time.time() + timeout
while time.time() < end:
try:
with open(LOG, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
except FileNotFoundError:
lines = []
for i in range(max(0, len(lines) - 1 - since), len(lines)):
if pattern in lines[i]:
return lines[i].strip()
time.sleep(0.2)
return None
open(LOG, 'w', encoding='utf-8').close()
proc = subprocess.Popen(
[BRIDGE, "--shm", SHM_NAME],
env={**os.environ, "SF_SHM_NAME": SHM_NAME, "SF_PARENT_PID": str(os.getpid()),
"SF_SAMPLE_RATE": "48000", "SF_BLOCK_SIZE": "256"},
stdout=open(LOG, 'ab'), stderr=subprocess.STDOUT,
)
time.sleep(1.5)
print("bridge pid", proc.pid, "poll", proc.poll())
if proc.poll() is not None:
print("!!! BRIDGE EXITED EARLY"); sys.exit(1)
def shot(hwnd, tag):
user32 = ctypes.windll.user32
rw = w.RECT()
user32.GetWindowRect(hwnd, ctypes.byref(rw))
wdt, hgt = rw.right - rw.left, rw.bottom - rw.top
if wdt <= 0 or hgt <= 0:
return f"bad rect {rw.left},{rw.top},{rw.right},{rw.bottom}"
png = os.path.join(HERE, f"probe_fix_{tag}.png")
ps = f"""
Add-Type -AssemblyName System.Drawing
$bmp = New-Object System.Drawing.Bitmap({wdt}, {hgt})
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.CopyFromScreen({rw.left}, {rw.top}, 0, 0, $bmp.Size)
$bmp.Save('{png}')
$g.Dispose(); $bmp.Dispose()
"""
subprocess.run(["powershell", "-NoProfile", "-Command", ps], check=False)
img = Image.open(png).convert('L')
px = list(img.getdata())
n = len(px)
avg = sum(px) / n
buckets = [0, 0, 0, 0, 0, 0, 0, 0]
for v in px:
buckets[v // 32] += 1
pct = [round(100 * b / n) for b in buckets]
return f"avgLum {round(avg,1)} buckets[0-31..224-255] {pct} size {img.size}"
def muted_now():
# bridge log "channel muted while editor open" appears while editor open;
# after close, next load/open should NOT show mute. We instead check writeIndex.
return None
# [1] load Nexus ch=0
since = len(open(LOG, 'rb').read().splitlines())
print("[1] load Nexus ch=0...")
push_controls([(2, 0, 0, 0, NEXUS)])
r = wait_log("instrument loaded ch=0", 90)
print(" ", r)
# [2] open GUI
since = len(open(LOG, 'rb').read().splitlines())
print("[2] open GUI ch=0...")
push_controls([(4, 0, 0, 0, "nexus")])
r = wait_log("GUI attached", 40)
print(" ", r)
m = re.search(r'hwnd=([0-9A-Fa-f]+)', r or "")
hwnd = int(m.group(1), 16) if m else None
time.sleep(8)
if hwnd:
print(" shot:", shot(hwnd, "nexus_open"))
alive = proc.poll() is None
print(" alive:", alive)
wi = write_index(); time.sleep(2); wi2 = write_index()
print(" writeIndex", wi, "->", wi2, "advancing:", wi2 > wi)
# [3] close GUI via WM_CLOSE
if hwnd:
ctypes.windll.user32.SendMessageW(hwnd, 0x0010, 0, 0)
time.sleep(4)
alive = proc.poll() is None
print("[3] after close alive:", alive)
# [4] reopen GUI (user symptom: reopen -> mute)
since = len(open(LOG, 'rb').read().splitlines())
print("[4] reopen GUI ch=0...")
push_controls([(4, 0, 0, 0, "nexus")])
r = wait_log("GUI attached", 40)
print(" ", r)
m = re.search(r'hwnd=([0-9A-Fa-f]+)', r or "")
hwnd = int(m.group(1), 16) if m else None
time.sleep(8)
if hwnd:
print(" shot:", shot(hwnd, "nexus_reopen"))
alive = proc.poll() is None
wi = write_index(); time.sleep(2); wi2 = write_index()
print(" alive:", alive, "writeIndex", wi, "->", wi2, "advancing:", wi2 > wi)
# [5] load OTHER plugin while GUI open (user symptom: not load)
since = len(open(LOG, 'rb').read().splitlines())
print("[5] load Ample while Nexus GUI open...")
push_controls([(2, 0, 0, 0, AMPLE)])
r = wait_log("instrument loaded ch=0", 60)
print(" ", r)
alive = proc.poll() is None
wi = write_index(); time.sleep(2); wi2 = write_index()
print(" alive:", alive, "writeIndex", wi, "->", wi2, "advancing:", wi2 > wi)
print("FINAL alive:", proc.poll() is None)
print("--- last 25 log lines ---")
for l in open(LOG, 'r', encoding='utf-8', errors='replace').readlines()[-25:]:
print(l.rstrip())
kernel32.UnmapViewOfFile.argtypes = [ctypes.c_void_p]
kernel32.CloseHandle.argtypes = [ctypes.c_void_p]
kernel32.UnmapViewOfFile(ptr)
kernel32.CloseHandle(hMap)
proc.terminate()
print("done")
+138
View File
@@ -0,0 +1,138 @@
# Probe GUI render: spawn bridge, load Nexus, open GUI ch=5, dump window tree + screenshot.
# Usage: python probe_gui.py [bridge_exe]
import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time, re
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
SHM_NAME = "SonicForge_DAW_IPC_GUIPROBE"
SIZE = 32768
BRIDGE = sys.argv[1] if len(sys.argv) > 1 else r"C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe"
LOG = os.path.join(os.path.dirname(os.path.abspath(__file__)), "probe_gui.log")
NEXUS = r"C:\Program Files\Common Files\VST3\Nexus.vst3"
AMPLE = r"C:\Program Files\Common Files\VST3\Ample Sound\ACDD.vst3"
user32 = ctypes.windll.user32
kernel32 = ctypes.windll.kernel32
kernel32.CreateFileMappingA.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_char_p]
kernel32.CreateFileMappingA.restype = ctypes.c_void_p
kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t]
kernel32.MapViewOfFile.restype = ctypes.c_void_p
INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
PAGE_READWRITE = 0x04
FILE_MAP_ALL_ACCESS = 0xF001F
hMap = kernel32.CreateFileMappingA(INVALID_HANDLE_VALUE, None, PAGE_READWRITE, 0, SIZE, SHM_NAME.encode())
ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0)
def w32(off, fmt, *args):
ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt))
def write_control(type_, arg0, arg1, ch, path):
for i in range(8):
off = 2836 + i * 1040
t = ctypes.c_uint32.from_address(ptr + off).value
if t == 0:
w32(off, 'IIII', type_, arg0, arg1, ch)
if path:
b = path.encode('utf-8')[:1023] + b'\x00'
ctypes.memmove(ptr + off + 16, b, len(b))
return i
raise RuntimeError("control queue full")
def push_controls(events):
for i in range(8):
w32(2836 + i * 1040, 'I', 0)
for (type_, arg0, arg1, ch, path) in events:
write_control(type_, arg0, arg1, ch, path)
w32(11156, 'I', len(events)) # controlQueueCount — bridge drains only count entries
proc = subprocess.Popen([BRIDGE, "--shm", SHM_NAME],
env={**os.environ, "SF_SHM_NAME": SHM_NAME, "SF_PARENT_PID": str(os.getpid()),
"SF_SAMPLE_RATE": "48000", "SF_BLOCK_SIZE": "256"},
stdout=open(LOG, 'wb'), stderr=subprocess.STDOUT)
time.sleep(1.5)
def wait_log(pattern, timeout, since=0):
t0 = time.time()
while time.time() - t0 < timeout:
try:
with open(LOG, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
for i in range(since, len(lines)):
if pattern in lines[i]:
return lines[i].strip(), i + 1
except FileNotFoundError:
pass
time.sleep(0.2)
return None, 0
push_controls([(2, 0, 0, 5, NEXUS)])
r, pos = wait_log("instrument loaded ch=5", 90)
print("[load]", r)
push_controls([(4, 0, 0, 5, "nexus")])
r, pos = wait_log("GUI attached", 30)
print("[open]", r)
hwnd = None
if r:
m = re.search(r'hwnd=([0-9A-Fa-f]+)', r)
if m:
hwnd = int(m.group(1), 16)
print("hwnd =", hex(hwnd) if hwnd else None)
time.sleep(5) # let the editor paint
def dump_tree(hwnd, depth=0):
out = []
buf = ctypes.create_unicode_buffer(256)
user32.GetClassNameW(hwnd, buf, 256)
user32.GetWindowTextW(hwnd, buf, 256)
title = buf.value
user32.GetClassNameW(hwnd, buf, 256)
cls = buf.value
r = w.RECT()
user32.GetWindowRect(hwnd, ctypes.byref(r))
vis = user32.IsWindowVisible(hwnd)
out.append(f"{' '*depth}h={hwnd:08X} cls='{cls}' title='{title}' rect=({r.left},{r.top},{r.right},{r.bottom}) vis={vis}")
children = []
@ctypes.WINFUNCTYPE(w.BOOL, w.HWND, w.LPARAM)
def cb(child, lp):
children.append(child)
return True
user32.EnumChildWindows(hwnd, cb, 0)
for c in children:
out.extend(dump_tree(c, depth + 1))
return out
print("--- window tree ---")
if hwnd:
for ln in dump_tree(hwnd):
print(ln)
# screenshot
if hwnd:
r = w.RECT()
user32.GetWindowRect(hwnd, ctypes.byref(r))
wdt, hgt = r.right - r.left, r.bottom - r.top
ps = f"""
Add-Type -AssemblyName System.Drawing
$bmp = New-Object System.Drawing.Bitmap({wdt}, {hgt})
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.CopyFromScreen({r.left}, {r.top}, 0, 0, $bmp.Size)
$bmp.Save('C:/Users/locpham/SonicForgeStudio/native_bridge/debug/probe_gui.png')
$g.Dispose(); $bmp.Dispose()
"""
subprocess.run(["powershell", "-NoProfile", "-Command", ps], check=False)
print("screenshot saved probe_gui.png")
# cleanup
kernel32.UnmapViewOfFile.argtypes = [ctypes.c_void_p]
kernel32.CloseHandle.argtypes = [ctypes.c_void_p]
kernel32.UnmapViewOfFile(ptr)
kernel32.CloseHandle(hMap)
try:
proc.terminate()
except Exception:
pass
time.sleep(0.5)
print("done")
+135
View File
@@ -0,0 +1,135 @@
# Probe Ample GUI render with current bridge exe.
import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time, re
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
SHM_NAME = "SonicForge_DAW_IPC_GUIPROBE2"
SIZE = 32768
BRIDGE = sys.argv[1] if len(sys.argv) > 1 else r"C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe"
LOG = os.path.join(os.path.dirname(os.path.abspath(__file__)), "probe_gui2.log")
PLUGIN = sys.argv[2] if len(sys.argv) > 2 else r"C:\Program Files\Common Files\VST3\Ample Sound\ACDD.vst3"
PID_TAG = sys.argv[3] if len(sys.argv) > 3 else "ample"
SHOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), f"probe_gui2_{PID_TAG}.png")
user32 = ctypes.windll.user32
kernel32 = ctypes.windll.kernel32
kernel32.CreateFileMappingA.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_char_p]
kernel32.CreateFileMappingA.restype = ctypes.c_void_p
kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t]
kernel32.MapViewOfFile.restype = ctypes.c_void_p
INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
PAGE_READWRITE = 0x04
FILE_MAP_ALL_ACCESS = 0xF001F
hMap = kernel32.CreateFileMappingA(INVALID_HANDLE_VALUE, None, PAGE_READWRITE, 0, SIZE, SHM_NAME.encode())
ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0)
def w32(off, fmt, *args):
ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt))
def write_control(type_, arg0, arg1, ch, path):
for i in range(8):
off = 2836 + i * 1040
t = ctypes.c_uint32.from_address(ptr + off).value
if t == 0:
w32(off, 'IIII', type_, arg0, arg1, ch)
if path:
b = path.encode('utf-8')[:1023] + b'\x00'
ctypes.memmove(ptr + off + 16, b, len(b))
return i
raise RuntimeError("control queue full")
def push_controls(events):
for i in range(8):
w32(2836 + i * 1040, 'I', 0)
for (type_, arg0, arg1, ch, path) in events:
write_control(type_, arg0, arg1, ch, path)
w32(11156, 'I', len(events))
proc = subprocess.Popen([BRIDGE, "--shm", SHM_NAME],
env={**os.environ, "SF_SHM_NAME": SHM_NAME, "SF_PARENT_PID": str(os.getpid()),
"SF_SAMPLE_RATE": "48000", "SF_BLOCK_SIZE": "256"},
stdout=open(LOG, 'wb'), stderr=subprocess.STDOUT)
time.sleep(1.5)
def wait_log(pattern, timeout, since=0):
t0 = time.time()
while time.time() - t0 < timeout:
try:
with open(LOG, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
for i in range(since, len(lines)):
if pattern in lines[i]:
return lines[i].strip(), i + 1
except FileNotFoundError:
pass
time.sleep(0.2)
return None, 0
push_controls([(2, 0, 0, 5, PLUGIN)])
r, pos = wait_log("instrument loaded ch=5", 90)
print("[load]", r)
push_controls([(4, 0, 0, 5, PID_TAG)])
r, pos = wait_log("GUI attached", 40)
print("[open]", r)
hwnd = None
if r:
m = re.search(r'hwnd=([0-9A-Fa-f]+)', r)
if m:
hwnd = int(m.group(1), 16)
print("hwnd =", hex(hwnd) if hwnd else None)
time.sleep(6) # let the editor paint
def dump_tree(hwnd, depth=0):
out = []
buf = ctypes.create_unicode_buffer(256)
user32.GetClassNameW(hwnd, buf, 256)
cls = buf.value
user32.GetWindowTextW(hwnd, buf, 256)
title = buf.value
r = w.RECT()
user32.GetWindowRect(hwnd, ctypes.byref(r))
vis = user32.IsWindowVisible(hwnd)
out.append(f"{' '*depth}h={hwnd:08X} cls='{cls}' title='{title}' rect=({r.left},{r.top},{r.right},{r.bottom}) vis={vis}")
children = []
@ctypes.WINFUNCTYPE(w.BOOL, w.HWND, w.LPARAM)
def cb(child, lp):
children.append(child)
return True
user32.EnumChildWindows(hwnd, cb, 0)
for c in children:
out.extend(dump_tree(c, depth + 1))
return out
print("--- window tree ---")
if hwnd:
for ln in dump_tree(hwnd):
print(ln)
if hwnd:
r = w.RECT()
user32.GetWindowRect(hwnd, ctypes.byref(r))
wdt, hgt = r.right - r.left, r.bottom - r.top
ps = f"""
Add-Type -AssemblyName System.Drawing
$bmp = New-Object System.Drawing.Bitmap({wdt}, {hgt})
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.CopyFromScreen({r.left}, {r.top}, 0, 0, $bmp.Size)
$bmp.Save('{SHOT}')
$g.Dispose(); $bmp.Dispose()
"""
subprocess.run(["powershell", "-NoProfile", "-Command", ps], check=False)
print("screenshot saved", SHOT)
kernel32.UnmapViewOfFile.argtypes = [ctypes.c_void_p]
kernel32.CloseHandle.argtypes = [ctypes.c_void_p]
kernel32.UnmapViewOfFile(ptr)
kernel32.CloseHandle(hMap)
try:
proc.terminate()
except Exception:
pass
time.sleep(0.5)
print("done")
+135
View File
@@ -0,0 +1,135 @@
# Probe Ample GUI render with current bridge exe.
import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time, re
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
SHM_NAME = "SonicForge_DAW_IPC_GUIPROBE2"
SIZE = 32768
BRIDGE = sys.argv[1] if len(sys.argv) > 1 else r"C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe"
LOG = os.path.join(os.path.dirname(os.path.abspath(__file__)), "probe_gui2.log")
PLUGIN = sys.argv[2] if len(sys.argv) > 2 else r"C:\Program Files\Common Files\VST3\Ample Sound\ACDD.vst3"
PID_TAG = sys.argv[3] if len(sys.argv) > 3 else "ample"
SHOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), f"probe_gui2_{PID_TAG}.png")
user32 = ctypes.windll.user32
kernel32 = ctypes.windll.kernel32
kernel32.CreateFileMappingA.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_char_p]
kernel32.CreateFileMappingA.restype = ctypes.c_void_p
kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t]
kernel32.MapViewOfFile.restype = ctypes.c_void_p
INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
PAGE_READWRITE = 0x04
FILE_MAP_ALL_ACCESS = 0xF001F
hMap = kernel32.CreateFileMappingA(INVALID_HANDLE_VALUE, None, PAGE_READWRITE, 0, SIZE, SHM_NAME.encode())
ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0)
def w32(off, fmt, *args):
ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt))
def write_control(type_, arg0, arg1, ch, path):
for i in range(8):
off = 2836 + i * 1040
t = ctypes.c_uint32.from_address(ptr + off).value
if t == 0:
w32(off, 'IIII', type_, arg0, arg1, ch)
if path:
b = path.encode('utf-8')[:1023] + b'\x00'
ctypes.memmove(ptr + off + 16, b, len(b))
return i
raise RuntimeError("control queue full")
def push_controls(events):
for i in range(8):
w32(2836 + i * 1040, 'I', 0)
for (type_, arg0, arg1, ch, path) in events:
write_control(type_, arg0, arg1, ch, path)
w32(11156, 'I', len(events))
proc = subprocess.Popen([BRIDGE, "--shm", SHM_NAME],
env={**os.environ, "SF_SHM_NAME": SHM_NAME, "SF_PARENT_PID": str(os.getpid()),
"SF_SAMPLE_RATE": "48000", "SF_BLOCK_SIZE": "256"},
stdout=open(LOG, 'wb'), stderr=subprocess.STDOUT)
time.sleep(1.5)
def wait_log(pattern, timeout, since=0):
t0 = time.time()
while time.time() - t0 < timeout:
try:
with open(LOG, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
for i in range(since, len(lines)):
if pattern in lines[i]:
return lines[i].strip(), i + 1
except FileNotFoundError:
pass
time.sleep(0.2)
return None, 0
push_controls([(2, 0, 0, 5, PLUGIN)])
r, pos = wait_log("instrument loaded ch=5", 90)
print("[load]", r)
push_controls([(4, 0, 0, 5, PID_TAG)])
r, pos = wait_log("GUI attached", 40)
print("[open]", r)
hwnd = None
if r:
m = re.search(r'hwnd=([0-9A-Fa-f]+)', r)
if m:
hwnd = int(m.group(1), 16)
print("hwnd =", hex(hwnd) if hwnd else None)
time.sleep(25) # let the editor paint
def dump_tree(hwnd, depth=0):
out = []
buf = ctypes.create_unicode_buffer(256)
user32.GetClassNameW(hwnd, buf, 256)
cls = buf.value
user32.GetWindowTextW(hwnd, buf, 256)
title = buf.value
r = w.RECT()
user32.GetWindowRect(hwnd, ctypes.byref(r))
vis = user32.IsWindowVisible(hwnd)
out.append(f"{' '*depth}h={hwnd:08X} cls='{cls}' title='{title}' rect=({r.left},{r.top},{r.right},{r.bottom}) vis={vis}")
children = []
@ctypes.WINFUNCTYPE(w.BOOL, w.HWND, w.LPARAM)
def cb(child, lp):
children.append(child)
return True
user32.EnumChildWindows(hwnd, cb, 0)
for c in children:
out.extend(dump_tree(c, depth + 1))
return out
print("--- window tree ---")
if hwnd:
for ln in dump_tree(hwnd):
print(ln)
if hwnd:
r = w.RECT()
user32.GetWindowRect(hwnd, ctypes.byref(r))
wdt, hgt = r.right - r.left, r.bottom - r.top
ps = f"""
Add-Type -AssemblyName System.Drawing
$bmp = New-Object System.Drawing.Bitmap({wdt}, {hgt})
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.CopyFromScreen({r.left}, {r.top}, 0, 0, $bmp.Size)
$bmp.Save('{SHOT}')
$g.Dispose(); $bmp.Dispose()
"""
subprocess.run(["powershell", "-NoProfile", "-Command", ps], check=False)
print("screenshot saved", SHOT)
kernel32.UnmapViewOfFile.argtypes = [ctypes.c_void_p]
kernel32.CloseHandle.argtypes = [ctypes.c_void_p]
kernel32.UnmapViewOfFile(ptr)
kernel32.CloseHandle(hMap)
try:
proc.terminate()
except Exception:
pass
time.sleep(0.5)
print("done")
+46
View File
@@ -0,0 +1,46 @@
# Screenshot an existing window by handle (decimal), dump tree, pixel stats.
import ctypes, ctypes.wintypes as w, subprocess, sys, os
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
user32 = ctypes.windll.user32
hwnd = int(sys.argv[1], 16) if len(sys.argv) > 1 else 0
out = sys.argv[2] if len(sys.argv) > 2 else "probe_win.png"
def dump_tree(hwnd, depth=0):
out = []
buf = ctypes.create_unicode_buffer(256)
user32.GetClassNameW(hwnd, buf, 256)
cls = buf.value
user32.GetWindowTextW(hwnd, buf, 256)
title = buf.value
r = w.RECT()
user32.GetWindowRect(hwnd, ctypes.byref(r))
vis = user32.IsWindowVisible(hwnd)
out.append(f"{' '*depth}h={hwnd:08X} cls='{cls}' title='{title}' rect=({r.left},{r.top},{r.right},{r.bottom}) vis={vis}")
children = []
@ctypes.WINFUNCTYPE(w.BOOL, w.HWND, w.LPARAM)
def cb(child, lp):
children.append(child)
return True
user32.EnumChildWindows(hwnd, cb, 0)
for c in children:
out.extend(dump_tree(c, depth + 1))
return out
print("--- window tree ---")
for ln in dump_tree(hwnd):
print(ln)
r = w.RECT()
user32.GetWindowRect(hwnd, ctypes.byref(r))
wdt, hgt = r.right - r.left, r.bottom - r.top
print("size", wdt, hgt)
ps = f"""
Add-Type -AssemblyName System.Drawing
$bmp = New-Object System.Drawing.Bitmap({wdt}, {hgt})
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.CopyFromScreen({r.left}, {r.top}, 0, 0, $bmp.Size)
$bmp.Save('C:/Users/locpham/SonicForgeStudio/native_bridge/debug/{out}')
$g.Dispose(); $bmp.Dispose()
"""
subprocess.run(["powershell", "-NoProfile", "-Command", ps], check=False)
print("saved", out)
+107
View File
@@ -0,0 +1,107 @@
# waitscan for gui_probe bridge_exact11 (PASS case): dump threads while the
# probe is attached, to compare against the hung bridge.
import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
LOG = os.path.join(os.path.dirname(os.path.abspath(__file__)), "waitscan_probe.log")
PROBE = r"C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\gui_probe.exe"
PATH = r"C:\Program Files\Common Files\VST3\Nexus.vst3"
def wait_log(pattern, timeout):
end = time.time() + timeout
while time.time() < end:
try:
with open(LOG, 'r', encoding='utf-8', errors='replace') as f:
if pattern in f.read():
return True
except FileNotFoundError:
pass
time.sleep(0.2)
return False
proc = subprocess.Popen([PROBE, PATH, "bridge_exact11", "60"],
stdout=open(LOG, 'wb'), stderr=subprocess.STDOUT)
pid = proc.pid
print("pid", pid)
if not wait_log("isPlatformTypeSupported=0", 120):
print("TIMEOUT"); sys.exit(1)
time.sleep(3)
k32 = ctypes.windll.kernel32
PROCESS_QUERY_INFORMATION = 0x0400
PROCESS_VM_READ = 0x0010
PROCESS_DUP_HANDLE = 0x0040
TH32CS_SNAPTHREAD = 0x4
THREAD_GET_CONTEXT = 0x0008
THREAD_QUERY_INFORMATION = 0x0040
THREAD_SUSPEND_RESUME = 0x0002
k32.OpenProcess.argtypes = [w.DWORD, w.BOOL, w.DWORD]
k32.OpenProcess.restype = ctypes.c_void_p
hProc = k32.OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_DUP_HANDLE, False, pid)
if not hProc:
print("OpenProcess failed", ctypes.get_last_error()); sys.exit(1)
psapi = ctypes.windll.psapi
class MODULEINFO(ctypes.Structure):
_fields_ = [("lpBaseOfDll", ctypes.c_void_p), ("SizeOfImage", w.DWORD),
("pad", w.DWORD), ("EntryPoint", ctypes.c_void_p)]
psapi.EnumProcessModulesEx.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p), w.DWORD, ctypes.POINTER(w.DWORD), w.DWORD]
psapi.GetModuleInformation.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.POINTER(MODULEINFO), w.DWORD]
psapi.GetModuleBaseNameA.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_char_p, w.DWORD]
nmods = w.DWORD(0)
psapi.EnumProcessModulesEx(hProc, None, 0, ctypes.byref(nmods), 3)
arr = (ctypes.c_void_p * (nmods.value // 8))()
psapi.EnumProcessModulesEx(hProc, arr, nmods.value, ctypes.byref(nmods), 3)
mods = []
for m in arr:
if not m: continue
mi = MODULEINFO()
psapi.GetModuleInformation(hProc, m, ctypes.byref(mi), ctypes.sizeof(MODULEINFO))
name = ctypes.create_string_buffer(260)
psapi.GetModuleBaseNameA(hProc, m, name, 260)
mods.append((mi.lpBaseOfDll, mi.SizeOfImage, name.value.decode('latin1')))
def resolve(addr):
for base, size, name in mods:
if base <= addr < base + size:
return f"{name}+0x{addr-base:x}"
return f"0x{addr:x}"
class THREADENTRY32(ctypes.Structure):
_fields_ = [("dwSize", w.DWORD), ("cntUsage", w.DWORD), ("th32ThreadID", w.DWORD),
("th32OwnerProcessID", w.DWORD), ("tpBasePri", w.LONG),
("tpDeltaPri", w.LONG), ("dwFlags", w.DWORD)]
te = THREADENTRY32()
te.dwSize = ctypes.sizeof(THREADENTRY32)
snap = k32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)
ok = k32.Thread32First(snap, ctypes.byref(te))
threads = []
while ok:
if te.th32OwnerProcessID == pid:
threads.append(te.th32ThreadID)
ok = k32.Thread32Next(snap, ctypes.byref(te))
k32.CloseHandle(snap)
print(f"threads: {threads}")
class CONTEXT(ctypes.Structure):
_fields_ = [("raw", ctypes.c_ubyte * 0x4d0)]
CONTEXT_AMD64 = 0x00100000
CONTEXT_FULL = CONTEXT_AMD64 | 0x3
def ctx_regs(ctx):
b = bytes(ctx.raw)
u = lambda o: struct.unpack_from('<Q', b, o)[0]
return dict(rip=u(0xF8), rsp=u(0x98), rcx=u(0x80), rdx=u(0x88), r8=u(0xB8), r9=u(0xC0))
k32.GetThreadContext.argtypes = [ctypes.c_void_p, ctypes.POINTER(CONTEXT)]
k32.GetThreadContext.restype = w.BOOL
k32.SuspendThread.argtypes = [ctypes.c_void_p]
k32.ResumeThread.argtypes = [ctypes.c_void_p]
k32.OpenThread.argtypes = [w.DWORD, w.BOOL, w.DWORD]
k32.OpenThread.restype = ctypes.c_void_p
k32.CloseHandle.argtypes = [ctypes.c_void_p]
for tid in threads:
ht = k32.OpenThread(THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | THREAD_SUSPEND_RESUME, False, tid)
if not ht:
print(f"tid {tid}: open fail"); continue
k32.SuspendThread(ht)
ctx = CONTEXT()
struct.pack_into('<I', ctx.raw, 0x30, CONTEXT_FULL)
if k32.GetThreadContext(ht, ctypes.byref(ctx)):
r = ctx_regs(ctx)
print(f"tid {tid}: rip={resolve(r['rip'])} rcx=0x{r['rcx']:x} r9=0x{r['r9']:x}")
else:
print(f"tid {tid}: GetThreadContext fail")
k32.ResumeThread(ht)
k32.CloseHandle(ht)
proc.terminate()