diff --git a/.gitignore b/.gitignore index e214bc0..93dcc90 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ obj bin installer/Output +# Explicitly ignore debug/release .pdb files for extra safety +bin/Debug/net6.0-windows/FlexASIOGUI.pdb +bin/Release/net6.0-windows/FlexASIOGUI.pdb +# Release builds do not include .pdb files by default, but this ensures no accidental inclusion diff --git a/FlexASIOGUI.csproj b/FlexASIOGUI.csproj index 45ddec8..0e59c2f 100644 --- a/FlexASIOGUI.csproj +++ b/FlexASIOGUI.csproj @@ -1,4 +1,4 @@ - + WinExe @@ -8,6 +8,10 @@ installer\flexasiogui.ico + + none + + @@ -20,4 +24,18 @@ + + + PreserveNewest + portaudio_x64.dll + + + + + + PreserveNewest + portaudio_x64.pdb + + + \ No newline at end of file diff --git a/Form1.cs b/Form1.cs index 21fb000..033fe76 100644 --- a/Form1.cs +++ b/Form1.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data; using System.Linq; @@ -8,6 +8,7 @@ using System.Diagnostics; using System.IO; using System.Globalization; +// Tomlyn is used instead of deprecated Nett library for TOML parsing (migrated in v0.35) using Tomlyn; using System.Runtime.InteropServices; @@ -24,6 +25,7 @@ public partial class Form1 : Form private readonly string flexasioVersion = "1.9"; private readonly string tomlName = "FlexASIO.toml"; private readonly string docUrl = "https://github.com/dechamps/FlexASIO/blob/master/CONFIGURATION.md"; + // Tomlyn library options for TOML serialization/deserialization TomlModelOptions tomlModelOptions = new(); [DllImport(@"C:\Program Files\FlexASIO\x64\FlexASIO.dll")] @@ -31,6 +33,53 @@ public partial class Form1 : Form [DllImport(@"kernel32.dll")] public static extern uint GetACP(); + // Direct PortAudio P/Invoke declarations for UTF-8 string handling + [DllImport("portaudio_x64.dll")] + private static extern IntPtr Pa_GetDeviceInfo(int device); + + [StructLayout(LayoutKind.Sequential)] + private struct PaDeviceInfo + { + public int structVersion; + public IntPtr name; // const char* - UTF-8 string + public int hostApi; + public int maxInputChannels; + public int maxOutputChannels; + public double defaultLowInputLatency; + public double defaultLowOutputLatency; + public double defaultHighInputLatency; + public double defaultHighOutputLatency; + public double defaultSampleRate; + } + + // Helper method to safely get device name as UTF-8 + private static string GetDeviceNameUTF8(int deviceIndex) + { + try + { + IntPtr deviceInfoPtr = Pa_GetDeviceInfo(deviceIndex); + if (deviceInfoPtr == IntPtr.Zero) + return string.Empty; + + PaDeviceInfo deviceInfo = Marshal.PtrToStructure(deviceInfoPtr); + if (deviceInfo.name == IntPtr.Zero) + return string.Empty; + + // Read the string as UTF-8 + int length = 0; + while (Marshal.ReadByte(deviceInfo.name, length) != 0) + length++; + + byte[] buffer = new byte[length]; + Marshal.Copy(deviceInfo.name, buffer, 0, length); + return Encoding.UTF8.GetString(buffer); + } + catch + { + return string.Empty; + } + } + public Form1() { InitializeComponent(); @@ -51,6 +100,7 @@ public Form1() TOMLPath = $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}\\{tomlName}"; + // Keep C# property names as-is when serializing/deserializing TOML (no case conversion) tomlModelOptions.ConvertPropertyName = (string name) => name; this.LoadFlexASIOConfig(TOMLPath); @@ -142,18 +192,27 @@ private TreeNode[] GetDevicesForBackend(string Backend, bool Input) if (apiInfo.name != Backend) continue; + // Use direct P/Invoke to get UTF-8 device name + string deviceName = GetDeviceNameUTF8(i); + + // Fallback to the old method if P/Invoke fails + if (string.IsNullOrEmpty(deviceName)) + { + deviceName = DescrambleUTF8(deviceInfo.name); + } + if (Input == true) { if (deviceInfo.maxInputChannels > 0) { - treeNodes.Add(new TreeNode(DescrambleUTF8(deviceInfo.name))); + treeNodes.Add(new TreeNode(deviceName)); } } else { if (deviceInfo.maxOutputChannels > 0) { - treeNodes.Add(new TreeNode(DescrambleUTF8(deviceInfo.name))); + treeNodes.Add(new TreeNode(deviceName)); } } } diff --git a/docs/CHANGELOG_RUTICE.md b/docs/CHANGELOG_RUTICE.md new file mode 100644 index 0000000..cb8a8ab --- /dev/null +++ b/docs/CHANGELOG_RUTICE.md @@ -0,0 +1,20 @@ +# Change Log (Rutice/ruticejp) + +This document summarizes the key changes and maintenance actions performed by Rutice ([ruticejp](https://github.com/ruticejp)) for this repository. + +--- + +## 2025-11-19 + +- Changed default git branch from `master` to `main` and deleted the old `master` branch. +- Created branch `fix-utf8-encoding` for minimal UTF-8 encoding fixes (Japanese audio device names). +- Added English documentation: `dotnet-eol.md` (.NET 6.0 EOL notice and migration recommendations), `fix-utf8-encoding.md` (Purpose and scope of UTF-8 encoding fix branch), `ENCODING_FIX.md` (Details of the UTF-8 encoding fix implementation) +- Added supplementary notes to documentation files, clarifying authorship and contact info. +- Built Debug and Release configurations, confirmed .pdb exclusion in Release builds. +- Updated `.gitignore` to explicitly exclude `.pdb` files for Debug/Release builds. +- Configured `.csproj` to prevent `.pdb` generation in Release builds. +- Cleaned up `bin` directory and rebuilt both Debug and Release outputs. + +--- + +*This changelog was created and maintained by Rutice ([ruticejp](https://github.com/ruticejp)).* diff --git a/docs/ENCODING_FIX.md b/docs/ENCODING_FIX.md new file mode 100644 index 0000000..cdaafac --- /dev/null +++ b/docs/ENCODING_FIX.md @@ -0,0 +1,153 @@ +# UTF-8 Encoding Fix for Audio Device Names + +## Problem + +The original FlexASIO GUI had character encoding issues when displaying audio device names containing non-ASCII characters. Specifically: + +- **Japanese** device names like `スピーカー` (Speaker) and `マイク` (Microphone) were displayed as garbled text: `繝槭う繧ッ`, `繧ケ繝斐・繧ォ繝シ` +- This issue affected all languages using non-ASCII characters + +## Root Cause + +The problem was caused by the `portaudio-sharp` wrapper library incorrectly interpreting UTF-8 encoded strings from the native PortAudio C library: + +1. PortAudio C API returns device names as UTF-8 encoded `const char*` strings +2. The `portaudio-sharp` managed wrapper uses default marshaling, which interprets the strings as ANSI (system code page) +3. On Japanese Windows (CP932/Shift-JIS), UTF-8 bytes were misinterpreted as Shift-JIS, causing mojibake (garbled text) + +## Solution + +We implemented a **P/Invoke (Platform Invocation Services)** approach to directly call the native PortAudio API and correctly decode UTF-8 strings: + +### Implementation + +```csharp +// Direct P/Invoke declaration to PortAudio C API +[DllImport("portaudio_x64.dll")] +private static extern IntPtr Pa_GetDeviceInfo(int device); + +[StructLayout(LayoutKind.Sequential)] +private struct PaDeviceInfo +{ + public int structVersion; + public IntPtr name; // const char* - UTF-8 string + public int hostApi; + public int maxInputChannels; + public int maxOutputChannels; + public double defaultLowInputLatency; + public double defaultLowOutputLatency; + public double defaultHighInputLatency; + public double defaultHighOutputLatency; + public double defaultSampleRate; +} + +// Helper method to safely read UTF-8 device names +private static string GetDeviceNameUTF8(int deviceIndex) +{ + try + { + IntPtr deviceInfoPtr = Pa_GetDeviceInfo(deviceIndex); + if (deviceInfoPtr == IntPtr.Zero) + return string.Empty; + + PaDeviceInfo deviceInfo = Marshal.PtrToStructure(deviceInfoPtr); + if (deviceInfo.name == IntPtr.Zero) + return string.Empty; + + // Read the string as UTF-8 + int length = 0; + while (Marshal.ReadByte(deviceInfo.name, length) != 0) + length++; + + byte[] buffer = new byte[length]; + Marshal.Copy(deviceInfo.name, buffer, 0, length); + return Encoding.UTF8.GetString(buffer); // Correct UTF-8 decoding + } + catch + { + return string.Empty; + } +} +``` + +### Key Points + +1. **Direct API Call**: Bypasses the `portaudio-sharp` wrapper entirely +2. **Manual UTF-8 Decoding**: Reads raw bytes and uses `Encoding.UTF8.GetString()` for proper decoding +3. **Fallback Support**: Falls back to the original `DescrambleUTF8` method if P/Invoke fails + +## Language Support + +This fix supports **all languages that use non-ASCII characters**, including but not limited to: + +### Verified Languages + +- ✅ **Japanese** (日本語) - e.g., `スピーカー`, `マイク` + +### Expected to Work + +- ✅ **Korean** (한국어) - e.g., `스피커`, `마이크` +- ✅ **Simplified Chinese** (简体中文) - e.g., `扬声器`, `麦克风` +- ✅ **Traditional Chinese** (繁體中文) - e.g., `揚聲器`, `麥克風` +- ✅ **Cyrillic** (Русский) - e.g., `Динамики`, `Микрофон` +- ✅ **Arabic** (العربية) - RTL (right-to-left) text +- ✅ **Hebrew** (עברית) - RTL (right-to-left) text +- ✅ **Thai** (ไทย) - Complex script +- ✅ **Greek** (Ελληνικά) +- ✅ **All other Unicode-supported languages** + +### Technical Reason + +Since the fix uses standard UTF-8 decoding (`Encoding.UTF8.GetString()`), it supports **all Unicode character sets** (covering 149+ writing systems). UTF-8 is a universal encoding that can represent any Unicode character. + +## Before and After + +### Before (Garbled Japanese Text) + +```text +Device: 繝槭う繧ッ +Device: 繧ケ繝斐・繧ォ繝シ +Device: 繧ケ繝・Ξ繧ェ 繝溘く繧オ繝シ +``` + +### After (Correct Japanese Text) + +```text +Device: マイク (Microphone) +Device: スピーカー (Speaker) +Device: ステレオ ミキサー (Stereo Mixer) +``` + +## Files Modified + +- **Form1.cs**: Added P/Invoke declarations and `GetDeviceNameUTF8()` helper method +- **FlexASIOGUI.csproj**: Fixed `portaudio_x64.dll` auto-copy configuration + +## Testing + +To verify the fix works on your system: + +1. Build the project: + + ```bash + dotnet build -c Release + ``` + +2. Run the application and check if device names with non-ASCII characters display correctly + +3. Test with devices that have names in different languages + +## Notes + +- This fix maintains backward compatibility with the original `DescrambleUTF8` fallback method +- The P/Invoke approach is preferred as it addresses the root cause rather than working around symptoms +- RTL (right-to-left) languages like Arabic and Hebrew are supported for text storage and retrieval, though UI display may require additional Windows Forms RTL settings + +## References + +- [PortAudio API Documentation](http://www.portaudio.com/docs/v19-doxydocs/portaudio_8h.html) +- [UTF-8 Encoding](https://en.wikipedia.org/wiki/UTF-8) +- [P/Invoke in .NET](https://docs.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke) + +--- +*Supplementary note by Rutice ([ruticejp](https://github.com/ruticejp))* diff --git a/docs/dotnet-eol.md b/docs/dotnet-eol.md new file mode 100644 index 0000000..0954b09 --- /dev/null +++ b/docs/dotnet-eol.md @@ -0,0 +1,35 @@ +# .NET Target Framework EOL (End Of Life) Notice + +## Current Status + +This project currently targets `net6.0-windows` as its framework. + +- `net6.0-windows` reached End Of Life (EOL) in November 2024. +- No further security updates or bug fixes will be provided for this version. +- For details, see the official [.NET Support Policy](https://aka.ms/dotnet-core-support). + +## Recommended Actions + +To ensure continued security and stability, it is recommended to: + +1. **Migrate to the latest LTS (Long Term Support) framework** + - Example: `net8.0-windows` +2. **Migration Steps** + - Change `` in `FlexASIOGUI.csproj` to `net8.0-windows` + - Check compatibility of dependencies and APIs + - Build and test the application + +## Notes + +- Some APIs or behaviors may change in newer frameworks. +- Refer to official documentation and migration guides for details. + +## Reference Links + +- [.NET Support Policy](https://aka.ms/dotnet-core-support) +- [.NET 8.0 Release Notes](https://learn.microsoft.com/dotnet/core/whats-new/dotnet-8) +- [Migration Guide](https://learn.microsoft.com/dotnet/core/porting/) + +--- + +*Supplementary note by Rutice ([ruticejp](https://github.com/ruticejp))* diff --git a/docs/fix-utf8-encoding.md b/docs/fix-utf8-encoding.md new file mode 100644 index 0000000..d81aacc --- /dev/null +++ b/docs/fix-utf8-encoding.md @@ -0,0 +1,20 @@ +# About the `fix-utf8-encoding` Branch + +This branch provides minimal support for UTF-8 encoding issues, specifically addressing garbled characters in Japanese audio device names. + +## Purpose + +- The main goal is to fix character corruption (mojibake) for Japanese device names in the application UI. +- Only essential changes for UTF-8 handling are included; no extensive refactoring or additional features are implemented. + +## Notes + +- This is not a comprehensive solution for all encoding problems. +- Further improvements or broader encoding support may be addressed in future branches. + +--- + +**This branch is intended for quick and minimal UTF-8 encoding fixes.** + +--- +*Supplementary note by Rutice ([ruticejp](https://github.com/ruticejp))*