using System; using System.Collections.Generic; using System.Text; using GeViSetEditor.Core.Models; namespace GeViSetEditor.Core.Parsers { /// /// Safe parser that preserves original binary data for round-trip conversion /// Only parses action mappings, keeps everything else as-is /// public class SafeSetFileParser { private byte[] _originalData; private int _position; public SafeSetFile Parse(byte[] data) { _originalData = data; _position = 0; var setFile = new SafeSetFile { OriginalData = data, FileSize = data.Length }; // Read header if (_position < data.Length && data[_position] == 0x00) _position++; string header = ReadPascalString(); setFile.Header = header ?? "GeViSoft Parameters"; Console.WriteLine($"Parsing .set file: {data.Length} bytes, header: '{setFile.Header}'"); // Find and extract all action mappings without disturbing the rest ExtractActionMappings(setFile); return setFile; } private void ExtractActionMappings(SafeSetFile setFile) { // Search for all "Rules" patterns (05 52 75 6C 65 73) byte[] rulesPattern = new byte[] { 0x05, 0x52, 0x75, 0x6C, 0x65, 0x73 }; for (int i = 0; i < _originalData.Length - rulesPattern.Length - 100; i++) { if (MatchesPattern(_originalData, i, rulesPattern)) { var mapping = TryExtractActionMapping(i); if (mapping != null) { setFile.ActionMappings.Add(mapping); } } } Console.WriteLine($"Extracted {setFile.ActionMappings.Count} action mappings"); } private ActionMappingEntry TryExtractActionMapping(int rulesOffset) { try { var entry = new ActionMappingEntry { FileOffset = rulesOffset, RulesMarkerOffset = rulesOffset }; // Position after "Rules" marker int pos = rulesOffset + 6; // Skip "05 Rules" // Read what appears to be count/metadata if (pos + 10 > _originalData.Length) return null; // Skip metadata bytes while (pos < _originalData.Length && _originalData[pos] <= 0x04) { pos++; } // Store start of action data int actionDataStart = pos; // Extract action strings using pattern 07 01 40 var actions = new List(); int consecutiveFailures = 0; int maxAttempts = 200; while (maxAttempts-- > 0 && pos < _originalData.Length - 10) { var action = TryReadActionStringAt(pos); if (action != null) { actions.Add(action); pos += 5 + Encoding.UTF8.GetByteCount(action); // 07 01 40 + 2-byte len + data consecutiveFailures = 0; } else { pos++; consecutiveFailures++; if (consecutiveFailures > 100) break; } } if (actions.Count > 0) { entry.Actions = actions; entry.ActionDataStartOffset = actionDataStart; entry.ActionDataEndOffset = pos; // Store original bytes for this mapping int length = pos - rulesOffset; entry.OriginalBytes = new byte[length]; Array.Copy(_originalData, rulesOffset, entry.OriginalBytes, 0, length); return entry; } } catch (Exception ex) { Console.WriteLine($"Error extracting mapping at offset {rulesOffset}: {ex.Message}"); } return null; } private string TryReadActionStringAt(int pos) { // Pattern: 07 01 40 if (pos + 5 > _originalData.Length) return null; if (_originalData[pos] != 0x07 || _originalData[pos + 1] != 0x01 || _originalData[pos + 2] != 0x40) return null; int length = BitConverter.ToUInt16(_originalData, pos + 3); if (length == 0 || length > 500 || pos + 5 + length > _originalData.Length) return null; try { string result = Encoding.UTF8.GetString(_originalData, pos + 5, length); // Validate it looks like an action string if (string.IsNullOrWhiteSpace(result) || result.Length < 3) return null; return result; } catch { return null; } } private bool MatchesPattern(byte[] data, int offset, byte[] pattern) { if (offset + pattern.Length > data.Length) return false; for (int i = 0; i < pattern.Length; i++) { if (data[offset + i] != pattern[i]) return false; } return true; } private string ReadPascalString() { if (_position + 2 > _originalData.Length || _originalData[_position] != 0x07) return null; byte length = _originalData[_position + 1]; if (_position + 2 + length > _originalData.Length || length == 0) return null; string result = Encoding.UTF8.GetString(_originalData, _position + 2, length); _position += 2 + length; return result; } } }