Skip to content

Read vdd_settings.xml saved in a Windows code page - #221

Merged
whoisaldo merged 3 commits into
release/0.3.5-candidatefrom
fix/vdd-settings-codepage
Nov 8, 2026
Merged

whoisaldo merged 3 commits into
release/0.3.5-candidatefrom
fix/vdd-settings-codepage

Conversation

@whoisaldo

@whoisaldo whoisaldo commented Nov 8, 2026 •

Copy link
Copy Markdown
Member

Some hosts logged Could not write vdd_settings.xml … stream did not contain valid UTF-8 and Left vdd_settings.xml as it is; the driver chooses the graphics card. In those cases the settings file had been saved in the system's ANSI code page, which Notepad's "ANSI" option and some older tools use. #207 reads UTF-8 and UTF-16 but still refused this file, so the second screen kept stale modes, and on hybrid laptops the host skipped the card pin.

What the driver reads

Virtual Display Driver 25.5.2 opens vdd_settings.xml with SHCreateStreamOnFileW, then calls CreateXmlReader and SetInput. It sets no MultiLanguage property or encoding hint. It reads until XmlLite reports an error and keeps whatever it read before that. I tested a native probe that makes the same calls on Windows 11:

  • UTF-8 and UTF-16 are read to the end, and so are single-byte declarations (windows-1252, ISO-8859-1).
  • Code pages 949 and 936 are never read fully:
    • When the declaration names the code page (euc-kr, ks_c_5601-1987, windows-949, cp949, uhc, gb2312, gbk, GB18030, windows-936, cp936), the reader fails on the declaration and reads nothing. This happens even when the rest of the file is ASCII.
    • When there is no declaration, or the driver's own encoding='utf-8' is kept as Notepad keeps it, the reader stops at the first non-ASCII byte.
  • The driver's own file saved as ANSI loses everything after the comment on line 59. A non-ASCII comment above the modes loses every setting.

Writing such a file back in its code page would keep the driver stopping at that byte. So the host writes it back as UTF-8 with an encoding="utf-8" declaration, which the reader takes to the end, and keeps the original beside it.

Change

  • Reading: an 8-bit file that declares a multi-byte code page (932, 936, 949, 950 or 54936, by number or by common name) is decoded with MultiByteToWideChar in that code page. A file with no declaration, or one declared UTF-8 whose bytes are not UTF-8, is decoded in the system ANSI code page (CP_ACP). The declaration is renamed to utf-8, as Keep Second screen working when the startup Disable hangs, after a lock, and with UTF-16 settings #207 does for UTF-16.
  • Mixed files: a UTF-8 file with stray bytes is not read in the ANSI code page. That covers a file that starts with a UTF-8 byte order mark, and, under a single-byte system code page, one that still holds UTF-8 beyond ASCII anywhere. Read in the code page, its UTF-8 text would be garbled. Text in a double-byte code page can look like UTF-8, so those code pages are not checked this way.
  • Writing: a file read from a code page is written back as UTF-8 even when its card stays (the card is already right, or another app named it), so the driver reads all of it. Before writing, the host copies the original to vdd_settings.xml.bak-<YYYYMMDD>. If today's backup already holds different bytes, the copy goes to -2, -3 and so on; a backup with the same bytes is reused. Each conversion logs Wrote vdd_settings.xml as UTF-8; the original is kept beside it once, with the backup's file name.
  • Unchanged: files with bytes that are not characters in that code page (MB_ERR_INVALID_CHARS), unknown encodings, and single-byte declarations, which the driver already reads, are left alone with one warning as before. The error now reads "the settings file is in no encoding this host reads".
  • No new crate: windows already enables Win32_Globalization.

Tests

  • Fixtures host/tests/fixtures/vdd_settings-25.5.2-cp949.xml and -cp936.xml: the vendor file with a non-ASCII comment holding a folder path and a non-ASCII card name, saved in that code page with the utf-8 declaration kept. They were made with Python's codecs, independent of the conversion under test.
  • a_code_page_settings_file_is_read_in_the_systems_code_page (Windows), the undeclared-legacy case: files declared UTF-8 and files with no declaration decode to the expected text in code page 949 or 936. The non-ASCII card reads back, and pinning a card changes only the card.
  • a_code_page_settings_file_is_written_back_as_utf8_with_a_backup (Windows), the declared case, with four names per code page:
    • apply_pin writes UTF-8 equal to the expected text, with only the card and the declaration changed.
    • The backup holds the original bytes, and a second write keeps it.
    • write_with_pin keeps the non-ASCII card another app named in the rewritten file.
  • a_settings_file_in_another_encoding_is_left_alone: its undeclared ANSI case is now read on Windows, which is this change; that case still runs off Windows. On Windows it is replaced by an unknown declared encoding and a cut double-byte character. Both stay byte for byte with no backup, also with owned = true.
  • a_file_in_the_systems_code_page_is_written_back_as_utf8 (Windows, system code page 1252): the driver's file saved as ANSI, with the utf-8 declaration kept and a card name outside ASCII, goes through the real CP_ACP path.
    • apply_pin converts the file whether the card is kept, unchanged or named.
    • write_with_pin rewrites the whole file and keeps the card.
    • The output is UTF-8 holding Radeon®, and the backup equals the original bytes.
  • The code page fixture test also covers Kept, Unchanged and pin_gpu_in with the same card: each still converts the file, with one backup.
  • utf8_with_a_stray_byte_is_not_read_in_a_single_byte_code_page (Windows), plus new left-alone cases on every platform: a byte order mark followed by a stray byte, and UTF-8 before or after a stray byte. Each stays byte for byte with no backup.
  • each_original_of_a_day_is_backed_up_once: backup numbering and reuse.
  • encoding_names_are_windows_code_pages: the name table and the backup date.

On the base, the file-level test fails: apply_pin returns an error for the code page fixture.

Checks:

  • macOS: cargo test -p eternal-host --lib, 1091 passed, and cargo clippy -p eternal-host --all-targets -- -D warnings.
  • Windows 11 (native, system code page 1252): cargo test -p eternal-host --lib, 1093 passed, including the four Windows-only tests, and the same clippy check.

A settings file saved in the system's ANSI code page, as Notepad does,
was not UTF-8 or UTF-16, so the host kept the driver's old modes and
skipped the graphics card pin.

The driver reads the file with XmlLite, which takes UTF-8 and UTF-16
but no double-byte code page: declared, it reads nothing; undeclared or
declared UTF-8, it stops at the first non-ASCII byte. The host now
decodes such a file in the code page its declaration names, or in the
system's ANSI code page, and writes it back as UTF-8 with a matching
declaration, which the driver reads to the end. The original is kept
as vdd_settings.xml.bak-<date> first. Bytes that are no character in
that code page leave the file alone, as before.
@whoisaldo whoisaldo added the 0.3.5 After 0.3.4 label Nov 8, 2026
A settings file read from a code page is now written back as UTF-8 even
when its graphics card stays, so the driver reads all of it.

UTF-8 with stray bytes is no longer read in the system's code page when
it starts with a byte order mark, or, in a single-byte code page, when
it still holds UTF-8 beyond ASCII; its UTF-8 text would be garbled.

A second conversion the same day keeps its original as
vdd_settings.xml.bak-<date>-<n>. Each conversion is logged once.

Tests cover the system code page through apply_pin and write_with_pin.
The mixed UTF-8 cases are refused only in a single-byte system code
page, so the file-level cases now run only there and both byte orders
are tested in code page 1252 directly. The backup test passes its date
instead of reading the clock.
@whoisaldo
whoisaldo marked this pull request as ready for review November 8, 2026 10:37
@whoisaldo
whoisaldo merged commit bbce161 into release/0.3.5-candidate Nov 8, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

0.3.5 After 0.3.4

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants