Skip to content

Commit

Permalink
Refactored kobopatch parsing (closes #22, fixes #24)
Browse files Browse the repository at this point in the history
  • Loading branch information
pgaskin committed Jul 17, 2019
1 parent 186ad9c commit 902d223
Show file tree
Hide file tree
Showing 6 changed files with 615 additions and 18 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ require (
github.com/stretchr/testify v0.0.0-20180319223459-c679ae2cc0cb
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8
gopkg.in/yaml.v2 v2.2.2
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22
)

replace gopkg.in/yaml.v3 => github.com/geek1011/yaml v0.0.0-20190717135119-db0123c0912e // v3-node-decodestrict
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/DataDog/czlib v0.0.0-20160811164712-4bc9a24e37f2 h1:/Xf0UwdF1dlv6cNu0
github.com/DataDog/czlib v0.0.0-20160811164712-4bc9a24e37f2/go.mod h1:xK7es18l1aQtR1g0i6RhEQ78ckvRAWiwlG0vBFud/Jk=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/geek1011/yaml v0.0.0-20190717135119-db0123c0912e h1:pvQRziv7p7KIPtCstDzbEbP+QDqXCLejUPi3ocALKkg=
github.com/geek1011/yaml v0.0.0-20190717135119-db0123c0912e/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
Expand Down
28 changes: 10 additions & 18 deletions kobopatch/kobopatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,18 @@ import (
"path/filepath"
"reflect"
"runtime"
"sort"
"strings"
"time"

"github.com/geek1011/kobopatch/patchlib"
"github.com/xi2/xz"

"github.com/geek1011/kobopatch/patchfile"
"github.com/geek1011/kobopatch/patchfile/kobopatch"
"github.com/geek1011/kobopatch/patchfile/kobopatchv2"
_ "github.com/geek1011/kobopatch/patchfile/patch32lsb"

"github.com/spf13/pflag"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)

var version = "unknown"
Expand Down Expand Up @@ -288,7 +287,9 @@ func (k *KoboPatch) LoadConfig(r io.Reader) error {
}

k.d("unmarshaling yaml")
err = yaml.UnmarshalStrict(buf, &k.Config)
dec := yaml.NewDecoder(bytes.NewReader(buf))
dec.KnownFields(true)
err = dec.Decode(&k.Config)
if err != nil {
k.d("--> %v", err)
return wrap(err, "error reading kobopatch.yaml")
Expand Down Expand Up @@ -648,26 +649,17 @@ func (k *KoboPatch) RunPatchTests() (map[string]map[string]error, error) {
return nil, wrap(err, "invalid patch file '%s'", pfn)
}

pf := reflect.ValueOf(ps).Interface().(*kobopatch.PatchSet)
res[pfn] = map[string]error{}

sortedNames := []string{}
for name := range *pf {
sortedNames = append(sortedNames, name)
}
sort.Strings(sortedNames)
sortedNames := reflect.ValueOf(ps).Interface().(*kobopatchv2.PatchSet).SortedNames()

errs := map[string]error{}
for _, name := range sortedNames {
fmt.Printf(" - %s", name)
err := errors.New("no such patch")
for pname, instructions := range *pf {
for _, instruction := range instructions {
if instruction.Enabled != nil {
*instruction.Enabled = pname == name
err = nil
break
}
var err error
for _, pname := range sortedNames {
if err = ps.SetEnabled(pname, pname == name); err != nil {
break
}
}
if err != nil {
Expand Down
223 changes: 223 additions & 0 deletions patchfile/kobopatchv2/kobopatch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package kobopatchv2

import (
"fmt"
"reflect"
"sort"

"github.com/geek1011/kobopatch/patchfile"
"github.com/geek1011/kobopatch/patchlib"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
)

type PatchSet struct {
parsed map[string]*parsedPatch
}

// parsedPatch holds a representation of a PatchNode for use internally. It
// cannot be re-marshaled directly (use the PatchNode and InstructionNode for
// that).
type parsedPatch struct {
Enabled bool
Description string
PatchGroup *string
Instructions []*parsedInstruction
}

// parsedInstruction holds a representation of a InstructionNode for use internally.
type parsedInstruction struct {
Index int
Line int
Instruction PatchableInstruction
}

func init() {
patchfile.RegisterFormat("kobopatch", Parse)
}

// Parse parses a PatchSet from a buf.
func Parse(buf []byte) (patchfile.PatchSet, error) {
patchfile.Log("parsing patch file: unmarshaling to map[string]yaml.Node\n")
var psn map[string]yaml.Node
if err := yaml.Unmarshal(buf, &psn); err != nil {
return nil, err
}

patchfile.Log("parsing patch file: converting to map[string]*parsedPatch\n")
ps := PatchSet{map[string]*parsedPatch{}}
for name, node := range psn {
patchfile.Log(" unmarshaling patch %#v to PatchNode ([]yaml.Node)\n", name)
var pn PatchNode
if err := node.DecodeStrict(&pn); err != nil {
return nil, errors.Wrapf(err, "line %d: patch %#v", node.Line, name)
}

patchfile.Log(" converting to []InstructionNode (map[string]yaml.Node)\n")
ns, err := pn.ToInstructionNodes()
if err != nil {
return nil, errors.Wrapf(err, "line %d: patch %#v", node.Line, name)
}

patchfile.Log(" converting to *parsedPatch\n")
ps.parsed[name] = &parsedPatch{}
for i, instNode := range ns {
patchfile.Log(" unmarshaling instruction %d to Instruction\n", i+1)
inst, err := instNode.ToInstruction()
if err != nil {
return nil, errors.Wrapf(err, "line %d: patch %#v: instruction %d", node.Line, name, i+1)
}

patchfile.Log(" converting to SingleInstruction...")
sinst := inst.ToSingleInstruction()
patchfile.Log(" type=%s\n", reflect.TypeOf(sinst))
switch sinst.(type) {
case Enabled:
ps.parsed[name].Enabled = bool(sinst.(Enabled))
case Description:
if ps.parsed[name].Description != "" {
return nil, errors.Errorf("patch %#v: line %d: instruction %d: duplicate Description instruction", name, instNode.Line(node.Line), i+1)
}
ps.parsed[name].Description = string(sinst.(Description))
case PatchGroup:
if ps.parsed[name].PatchGroup != nil {
return nil, errors.Errorf("patch %#v: line %d: instruction %d: duplicate PatchGroup instruction", name, instNode.Line(node.Line), i+1)
}
g := string(sinst.(PatchGroup))
ps.parsed[name].PatchGroup = &g
default:
patchfile.Log(" converting to PatchableInstruction\n")
if psinst, ok := sinst.(PatchableInstruction); ok {
ps.parsed[name].Instructions = append(ps.parsed[name].Instructions, &parsedInstruction{i + 1, instNode.Line(node.Line), psinst})
break
}
panic(fmt.Errorf("incomplete implementation (missing implementation of PatchableInstruction) for type %s", reflect.TypeOf(sinst)))
}
}
}
return &ps, nil
}

// ApplyTo applies a PatchSet to a Patcher.
func (ps *PatchSet) ApplyTo(pt *patchlib.Patcher) error {
patchfile.Log("validating patch file\n")
if err := ps.Validate(); err != nil {
err = errors.Wrap(err, "invalid patch file")
fmt.Printf(" Error: %v\n", err)
return err
}

patchfile.Log("looping over patches\n")
for _, name := range ps.SortedNames() {
patch := ps.parsed[name]
patchfile.Log(" Patch(%#v) enabled=%t\n", name, patch.Enabled)

patchfile.Log(" ResetBaseAddress()\n")
pt.ResetBaseAddress()

if !patch.Enabled {
patchfile.Log(" skipping\n")
fmt.Printf(" SKIP `%s`\n", name)
continue
}

patchfile.Log(" applying\n")
fmt.Printf(" APPLY `%s`\n", name)

patchfile.Log(" looping over instructions\n")
for _, inst := range patch.Instructions {
patchfile.Log(" %s index=%d line=%d\n", reflect.TypeOf(inst.Instruction), inst.Index, inst.Line)
if err := inst.Instruction.ApplyTo(pt, func(format string, a ...interface{}) {
patchfile.Log(" %s\n", fmt.Sprintf(format, a...))
}); err != nil {
err = errors.Wrapf(err, "could not apply patch %#v: line %d: inst %d", name, inst.Line, inst.Index)
patchfile.Log(" %v", err)
fmt.Printf(" Error: %v\n", err)
return err
}
}
}

return nil
}

// SetEnabled sets the Enabled state of a Patch in a PatchSet.
func (ps *PatchSet) SetEnabled(patch string, enabled bool) error {
if patch, ok := ps.parsed[patch]; ok {
patch.Enabled = enabled
return nil
}
return errors.Errorf("no such patch %#v", patch)
}

// SortedNames gets the names of patches sorted alphabetically.
func (ps *PatchSet) SortedNames() []string {
names := make([]string, len(ps.parsed))
var i int
for name := range ps.parsed {
names[i] = name
i++
}
sort.Strings(names)
return names
}

// Validate validates the PatchSet.
func (ps *PatchSet) Validate() error {
usedPatchGroups := map[string]string{}
for _, name := range ps.SortedNames() {
patch := ps.parsed[name]

if patch.PatchGroup != nil && patch.Enabled {
if r, ok := usedPatchGroups[*patch.PatchGroup]; ok {
return errors.Errorf("patch %#v: more than one patch enabled in PatchGroup %#v (other patch is %#v)", name, *patch.PatchGroup, r)
}
usedPatchGroups[*patch.PatchGroup] = name
}

if len(patch.Instructions) == 0 {
return errors.Errorf("patch %#v: no instructions which modify anything", name)
}

for _, inst := range patch.Instructions {
pfx := fmt.Sprintf("patch %#v: line %d: inst %d", name, inst.Line, inst.Index)
switch inst.Instruction.(type) {
case ReplaceBytesNOP:
if len(inst.Instruction.(ReplaceBytesNOP).Find)%2 != 0 {
return errors.Errorf("%s: ReplaceBytesNOP: find must be a multiple of 2 to be replaced with 00 46 (MOV r0, r0)", pfx)
}
case ReplaceString:
if inst.Instruction.(ReplaceString).MustMatchLength {
if d := len(inst.Instruction.(ReplaceString).Replace) - len(inst.Instruction.(ReplaceString).Find); d < 0 {
return errors.Errorf("%s: ReplaceString: replacement string %d too short", pfx, d)
} else if d > 0 {
return errors.Errorf("%s: ReplaceString: replacement string %d too long", pfx, -d)
}
}
case FindReplaceString:
if inst.Instruction.(FindReplaceString).MustMatchLength {
if d := len(inst.Instruction.(FindReplaceString).Replace) - len(inst.Instruction.(FindReplaceString).Find); d < 0 {
return errors.Errorf("%s: ReplaceString: replacement string %d too short", pfx, d)
} else if d > 0 {
return errors.Errorf("%s: ReplaceString: replacement string %d too long", pfx, -d)
}
}
case FindZlibHash:
if len(inst.Instruction.(FindZlibHash)) != 40 {
return errors.Errorf("%s: FindZlibHash: hash must be 40 chars long", pfx)
}
case ReplaceZlibGroup:
r := inst.Instruction.(ReplaceZlibGroup)
if len(r.Replacements) == 0 {
return errors.Errorf("%s: ReplaceZlibGroup: no replacements specified", pfx)
}
for i, repl := range r.Replacements {
if repl.Find == "" || repl.Replace == "" {
return errors.Errorf("%s: ReplaceZlibGroup: replacement %d: Find and Replace must be set", i+1)
}
}
}
}
}
return nil
}
Loading

0 comments on commit 902d223

Please sign in to comment.