397 lines
9.4 KiB
Go
397 lines
9.4 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),
|
|
}
|
|
}
|
|
|
|
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
|
|
if strings.HasPrefix(condition, "var:") {
|
|
varName := condition[4:]
|
|
return ctx.hasVar(varName)
|
|
}
|
|
if strings.HasPrefix(condition, "!var:") {
|
|
varName := condition[5:]
|
|
return !ctx.hasVar(varName)
|
|
}
|
|
|
|
// Version comparisons
|
|
if strings.HasPrefix(condition, "version") {
|
|
return ctx.evaluateVersionCondition(condition)
|
|
}
|
|
|
|
// String length operators: name!>5, name!<=10
|
|
stringOps := []string{"!>=", "!<=", "!>", "!<", "!="}
|
|
for _, op := range stringOps {
|
|
if idx := strings.Index(condition, op); idx > 0 {
|
|
varName := condition[:idx]
|
|
valueStr := condition[idx+len(op):]
|
|
return ctx.evaluateStringLength(varName, valueStr, op)
|
|
}
|
|
}
|
|
|
|
// Comparison operators: >=, <=, >, <, ==, !=
|
|
compOps := []string{">=", "<=", ">", "<", "==", "!="}
|
|
for _, op := range compOps {
|
|
if idx := strings.Index(condition, op); idx > 0 {
|
|
varName := condition[:idx]
|
|
valueStr := condition[idx+len(op):]
|
|
return ctx.evaluateComparison(varName, valueStr, op)
|
|
}
|
|
}
|
|
|
|
// Simple variable existence
|
|
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
|
|
}
|