package parser import ( "encoding/binary" "eq2emu/internal/common" "strconv" "strings" "unsafe" ) type ParseContext struct { data []byte offset int version uint32 flags uint64 vars map[string]any arrayStack []int } func NewContext(data []byte, version uint32, flags uint64) *ParseContext { return &ParseContext{ data: data, version: version, flags: flags, vars: make(map[string]any), } } func (ctx *ParseContext) readUint8() uint8 { val := ctx.data[ctx.offset] ctx.offset++ return val } func (ctx *ParseContext) readUint16() uint16 { val := binary.LittleEndian.Uint16(ctx.data[ctx.offset:]) ctx.offset += 2 return val } func (ctx *ParseContext) readUint32() uint32 { val := binary.LittleEndian.Uint32(ctx.data[ctx.offset:]) ctx.offset += 4 return val } func (ctx *ParseContext) readUint64() uint64 { val := binary.LittleEndian.Uint64(ctx.data[ctx.offset:]) ctx.offset += 8 return val } func (ctx *ParseContext) readOversizedUint8(threshold int) uint8 { if ctx.data[ctx.offset] == byte(threshold) { ctx.offset++ return ctx.readUint8() } return ctx.readUint8() } func (ctx *ParseContext) readOversizedUint16(threshold int) uint16 { if ctx.data[ctx.offset] == byte(threshold) { ctx.offset++ return ctx.readUint16() } return uint16(ctx.readUint8()) } func (ctx *ParseContext) readOversizedUint32(threshold int) uint32 { if ctx.data[ctx.offset] == byte(threshold) { ctx.offset++ return ctx.readUint32() } return uint32(ctx.readUint16()) } func (ctx *ParseContext) readString8() string { length := ctx.readUint8() str := string(ctx.data[ctx.offset : ctx.offset+int(length)]) ctx.offset += int(length) return str } func (ctx *ParseContext) readString16() string { length := ctx.readUint16() str := string(ctx.data[ctx.offset : ctx.offset+int(length)]) ctx.offset += int(length) return str } func (ctx *ParseContext) readEQ2String8() common.EQ2String8 { size := ctx.readUint8() data := string(ctx.data[ctx.offset : ctx.offset+int(size)]) ctx.offset += int(size) return common.EQ2String8{ Size: size, Data: data, } } func (ctx *ParseContext) readEQ2String16() common.EQ2String16 { size := ctx.readUint16() data := string(ctx.data[ctx.offset : ctx.offset+int(size)]) ctx.offset += int(size) return common.EQ2String16{ Size: size, Data: data, } } func (ctx *ParseContext) readEQ2String32() common.EQ2String32 { size := ctx.readUint32() data := string(ctx.data[ctx.offset : ctx.offset+int(size)]) ctx.offset += int(size) return common.EQ2String32{ Size: size, Data: data, } } func (ctx *ParseContext) readEQ2Color() common.EQ2Color { return common.EQ2Color{ Red: ctx.readUint8(), Green: ctx.readUint8(), Blue: ctx.readUint8(), } } func (ctx *ParseContext) readEQ2Equipment() common.EQ2EquipmentItem { return common.EQ2EquipmentItem{ Type: ctx.readUint16(), Color: ctx.readEQ2Color(), Highlight: ctx.readEQ2Color(), } } func (ctx *ParseContext) readBytes(count int) []byte { val := make([]byte, count) copy(val, ctx.data[ctx.offset:ctx.offset+count]) ctx.offset += count return val } func (ctx *ParseContext) readFloat32() float32 { val := ctx.readUint32() return *(*float32)(unsafe.Pointer(&val)) } func (ctx *ParseContext) readFloat64() float64 { val := ctx.readUint64() return *(*float64)(unsafe.Pointer(&val)) } func (ctx *ParseContext) setVar(name string, value any) { ctx.vars[name] = value } func (ctx *ParseContext) pushArrayIndex(index int) { ctx.arrayStack = append(ctx.arrayStack, index) } func (ctx *ParseContext) popArrayIndex() { if len(ctx.arrayStack) > 0 { ctx.arrayStack = ctx.arrayStack[:len(ctx.arrayStack)-1] } } // Condition evaluation methods func (ctx *ParseContext) checkCondition(condition string) bool { if condition == "" { return true } // Handle comma-separated OR conditions if strings.Contains(condition, ",") { parts := strings.Split(condition, ",") for _, part := range parts { if ctx.evaluateCondition(strings.TrimSpace(part)) { return true } } return false } // Handle AND conditions with & if strings.Contains(condition, "&") && !strings.Contains(condition, "0x") { parts := strings.Split(condition, "&") for _, part := range parts { if !ctx.evaluateCondition(strings.TrimSpace(part)) { return false } } return true } return ctx.evaluateCondition(condition) } func (ctx *ParseContext) evaluateCondition(condition string) bool { // Flag conditions: flag:name or !flag:name if strings.HasPrefix(condition, "flag:") { flagName := condition[5:] return (ctx.flags & ctx.getFlagValue(flagName)) != 0 } if strings.HasPrefix(condition, "!flag:") { flagName := condition[6:] return (ctx.flags & ctx.getFlagValue(flagName)) == 0 } // Variable conditions: var:name or !var:name (with %i support) if strings.HasPrefix(condition, "var:") { varName := ctx.resolveVariableName(condition[4:]) return ctx.hasVar(varName) } if strings.HasPrefix(condition, "!var:") { varName := ctx.resolveVariableName(condition[5:]) return !ctx.hasVar(varName) } // Version comparisons if strings.HasPrefix(condition, "version") { return ctx.evaluateVersionCondition(condition) } // Bitwise AND: header_flag&0x01 (with %i support) if strings.Contains(condition, "&0x") { parts := strings.SplitN(condition, "&", 2) varName := ctx.resolveVariableName(parts[0]) hexValue, _ := strconv.ParseUint(parts[1], 0, 64) varValue := ctx.getVarValue(varName) return (varValue & hexValue) != 0 } // String length operators: name!>5, name!<=10 (with %i support) stringOps := []string{"!>=", "!<=", "!>", "!<", "!="} for _, op := range stringOps { if idx := strings.Index(condition, op); idx > 0 { varName := ctx.resolveVariableName(condition[:idx]) valueStr := condition[idx+len(op):] return ctx.evaluateStringLength(varName, valueStr, op) } } // Comparison operators: >=, <=, >, <, ==, != (with %i support) compOps := []string{">=", "<=", ">", "<", "==", "!="} for _, op := range compOps { if idx := strings.Index(condition, op); idx > 0 { varName := ctx.resolveVariableName(condition[:idx]) valueStr := condition[idx+len(op):] return ctx.evaluateComparison(varName, valueStr, op) } } // Simple variable existence (with %i support) resolvedName := ctx.resolveVariableName(condition) return ctx.hasVar(resolvedName) } func (ctx *ParseContext) evaluateVersionCondition(condition string) bool { if strings.Contains(condition, ">=") { versionStr := condition[strings.Index(condition, ">=")+2:] targetVersion, _ := strconv.ParseUint(versionStr, 10, 32) return ctx.version >= uint32(targetVersion) } if strings.Contains(condition, "<=") { versionStr := condition[strings.Index(condition, "<=")+2:] targetVersion, _ := strconv.ParseUint(versionStr, 10, 32) return ctx.version <= uint32(targetVersion) } if strings.Contains(condition, ">") { versionStr := condition[strings.Index(condition, ">")+1:] targetVersion, _ := strconv.ParseUint(versionStr, 10, 32) return ctx.version > uint32(targetVersion) } if strings.Contains(condition, "<") { versionStr := condition[strings.Index(condition, "<")+1:] targetVersion, _ := strconv.ParseUint(versionStr, 10, 32) return ctx.version < uint32(targetVersion) } return false } func (ctx *ParseContext) evaluateStringLength(varName, valueStr, op string) bool { resolvedName := ctx.resolveVariableName(varName) str := ctx.getStringVar(resolvedName) targetLen, _ := strconv.Atoi(valueStr) strLen := len(str) switch op { case "!>": return strLen > targetLen case "!<": return strLen < targetLen case "!>=": return strLen >= targetLen case "!<=": return strLen <= targetLen case "!=": return strLen != targetLen } return false } func (ctx *ParseContext) evaluateComparison(varName, valueStr, op string) bool { resolvedName := ctx.resolveVariableName(varName) varValue := ctx.getVarValue(resolvedName) targetValue, _ := strconv.ParseUint(valueStr, 0, 64) switch op { case ">=": return varValue >= targetValue case "<=": return varValue <= targetValue case ">": return varValue > targetValue case "<": return varValue < targetValue case "==": return varValue == targetValue case "!=": return varValue != targetValue } return false } func (ctx *ParseContext) hasVar(name string) bool { if val, exists := ctx.vars[name]; exists { switch v := val.(type) { case uint8, uint16, uint32, uint64: return ctx.getVarValue(name) != 0 case string: return v != "" case common.EQ2String8: return v.Data != "" case common.EQ2String16: return v.Data != "" case common.EQ2String32: return v.Data != "" default: return true } } return false } func (ctx *ParseContext) getVarValue(name string) uint64 { if val, exists := ctx.vars[name]; exists { switch v := val.(type) { case uint8: return uint64(v) case uint16: return uint64(v) case uint32: return uint64(v) case uint64: return v } } return 0 } func (ctx *ParseContext) getStringVar(name string) string { if val, exists := ctx.vars[name]; exists { switch v := val.(type) { case string: return v case common.EQ2String8: return v.Data case common.EQ2String16: return v.Data case common.EQ2String32: return v.Data } } return "" } func (ctx *ParseContext) getFlagValue(flagName string) uint64 { flagMap := map[string]uint64{ "loot": 0x01, "has_equipment": 0x02, "no_colors": 0x04, } if val, exists := flagMap[flagName]; exists { return val } return 0 } func (ctx *ParseContext) getArraySize(condition string) int { if strings.HasPrefix(condition, "var:") { varName := condition[4:] return int(ctx.getVarValue(varName)) } return 0 } func (ctx *ParseContext) resolveVariableName(name string) string { // Handle %i substitution for array contexts if strings.Contains(name, "%i") && len(ctx.arrayStack) > 0 { currentIndex := ctx.arrayStack[len(ctx.arrayStack)-1] return strings.ReplaceAll(name, "%i", strconv.Itoa(currentIndex)) } return name } func (ctx *ParseContext) setVarWithArrayIndex(name string, value any) { // Always set the base variable name ctx.vars[name] = value // If we're in an array context, also set the indexed variable if len(ctx.arrayStack) > 0 { currentIndex := ctx.arrayStack[len(ctx.arrayStack)-1] indexedName := name + "_" + strconv.Itoa(currentIndex) ctx.vars[indexedName] = value } }