448 lines
11 KiB
Go

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),
}
}
// Unsigned integer readers
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
}
// Signed integer readers
func (ctx *ParseContext) readSint8() int8 {
val := int8(ctx.data[ctx.offset])
ctx.offset++
return val
}
func (ctx *ParseContext) readSint16() int16 {
val := int16(binary.LittleEndian.Uint16(ctx.data[ctx.offset:]))
ctx.offset += 2
return val
}
func (ctx *ParseContext) readSint32() int32 {
val := int32(binary.LittleEndian.Uint32(ctx.data[ctx.offset:]))
ctx.offset += 4
return val
}
func (ctx *ParseContext) readSint64() int64 {
val := int64(binary.LittleEndian.Uint64(ctx.data[ctx.offset:]))
ctx.offset += 8
return val
}
// Oversized readers
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) readOversizedSint16(threshold int) int16 {
val := int8(ctx.data[ctx.offset])
if val == int8(threshold) || val == int8(-threshold) {
ctx.offset++
return ctx.readSint16()
}
return int16(ctx.readSint8())
}
func (ctx *ParseContext) readEQ2String8() common.EQ2String8 {
size := ctx.readSint8()
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.readSint16()
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.readSint32()
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.readSint8(),
Green: ctx.readSint8(),
Blue: ctx.readSint8(),
}
}
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, int8, int16, int32, int64:
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
case int8:
return uint64(v)
case int16:
return uint64(v)
case int32:
return uint64(v)
case int64:
return uint64(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
}
}