Repository: d0c-s4vage/pfp (python)

Issue:
Memcmp is not implemented
After creating the issue, checkboxes will appear where `[] label` exist in the
markdown. You can check/uncheck them to fill out the environment section.

## Checklist

- [ ] I have included the [relevant portions of the] 010 template used that caused the bug
- [ ] I have filled out the environment section

## Environment

**Platform** 

- [ ] Windows
- [ ] Mac
- [ ] Linux
- [ ] Other (please specify)

**Python Version**

- [ ] Python 2.7
- [ ] Python 3.4
- [ ] Python 3.5
- [ ] Python 3.6
- [ ] Python 3.7
- [ ] Python 3.8
- [ ] Other (please specify)

## Describe the bug

Memcmp isn't implemented

## Stack Trace

```
Traceback (most recent call last):
  File "/tmp/venv3/bin/pfp", line 8, in <module>
    sys.exit(main())
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/__main__.py", line 55, in main
    keep_successful=args.keep,
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/__init__.py", line 91, in parse
    printf=printf,
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 788, in parse
    res = self._run(keep_successful)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 992, in _run
    traceback,
  File "/tmp/venv3/lib/python3.6/site-packages/six.py", line 695, in reraise
    raise value.with_traceback(tb)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 965, in _run
    res = self._handle_node(self._ast, None, None, self._stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 1074, in _handle_node
    res = self._node_switch[node.__class__](node, scope, ctxt, stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 1117, in _handle_file_ast
    self._handle_node(child, scope, ctxt, stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 1074, in _handle_node
    res = self._node_switch[node.__class__](node, scope, ctxt, stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 2341, in _handle_while
    self._handle_node(node.stmt, scope, ctxt, stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 1074, in _handle_node
    res = self._node_switch[node.__class__](node, scope, ctxt, stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 2155, in _handle_compound
    self._handle_node(child, scope, ctxt, stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 1074, in _handle_node
    res = self._node_switch[node.__class__](node, scope, ctxt, stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 1339, in _handle_decl
    field._pfp__init(stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 126, in _pfp__init
    self._pfp__interp._handle_node(decls, ctxt=self, stream=stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 1074, in _handle_node
    res = self._node_switch[node.__class__](node, scope, ctxt, stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 1636, in _handle_struct_decls
    self._handle_node(decl, scope, ctxt, stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 1074, in _handle_node
    res = self._node_switch[node.__class__](node, scope, ctxt, stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 2283, in _handle_if
    cond = self._handle_node(node.cond, scope, ctxt, stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 1074, in _handle_node
    res = self._node_switch[node.__class__](node, scope, ctxt, stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 1802, in _handle_binary_op
    left_val = self._handle_node(node.left, scope, ctxt, stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 1074, in _handle_node
    res = self._node_switch[node.__class__](node, scope, ctxt, stream)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/interp.py", line 2122, in _handle_func_call
    return func.call(func_args, ctxt, scope, stream, self, node.coord)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/functions.py", line 79, in call
    res = self.func(args, ctxt, scope, stream, coord)
  File "/tmp/venv3/lib/python3.6/site-packages/pfp/native/compat_string.py", line 140, in Memcmp
    raise NotImplementedError()
pfp.errors.PfpError:
Exception at tests/templates/MP4.bt:53
```

## To Reproduce

Use the MP4.bt template from https://github.com/d0c-s4vage/py010parser/issues/17 ([MP4.txt](https://github.com/d0c-s4vage/py010parser/files/745661/MP4.txt))

## Expected Behavior

The MP4 template should be able to be used.

## Implementation/Fix Notes/Thoughts

Implement the `Memcmp` function in `native/compat_string.py`: https://github.com/d0c-s4vage/pfp/blob/master/pfp/native/compat_string.py#L137


Pull request:
Added Memcmp implementation and tests
fixes #100 

Patch:
diff --git a/pfp/native/compat_string.py b/pfp/native/compat_string.py
index 4eb658f..ebbf9e6 100644
--- a/pfp/native/compat_string.py
+++ b/pfp/native/compat_string.py
@@ -137,7 +137,25 @@ def IntToBinaryStr(params, ctxt, scope, stream, coord):
 # int Memcmp( const uchar s1[], const uchar s2[], int n )
 @native(name="Memcmp", ret=pfp.fields.Int)
 def Memcmp(params, ctxt, scope, stream, coord):
-    raise NotImplementedError()
+    """
+    int Memcmp( const uchar s1[], const uchar s2[], int n )
+
+    Compares the first n bytes of s1 and s2. Returns a value less than zero if
+    s1 is less than s2, zero if they are equal, or a value greater than zero if
+    s1 is greater than s2.
+    """
+    if len(params) < 3:
+        raise errors.InvalidArguments(
+            coord, "{} args".format(len(params)), "3 arguments",
+        )
+    s1 = PYSTR(params[0])
+    s2 = PYSTR(params[1])
+    n = PYVAL(params[2])
+
+    s1_sub = s1[:n]
+    s2_sub = s2[:n]
+
+    return _cmp(s1_sub, s2_sub)
 
 
 # void Memcpy( uchar dest[], const uchar src[], int n, int destOffset=0, int srcOffset=0 )


Test patch:
diff --git a/tests/test_compat.py b/tests/test_compat.py
index b02221f..d6b6245 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -501,6 +501,23 @@ class TestCompatString(utils.PfpTestCase):
             stdout="-1",
         )
 
+    def test_memcmp(self):
+        dom = self._test_parse_build(
+            "",
+            r"""
+            local string a = "hellothere";
+            local string b = "helloblah";
+            Printf("%d\n", Memcmp(a, b, 1));
+            Printf("%d\n", Memcmp(a, b, 2));
+            Printf("%d\n", Memcmp(a, b, 3));
+            Printf("%d\n", Memcmp(a, b, 4));
+            Printf("%d\n", Memcmp(a, b, 5));
+            Printf("%d\n", Memcmp(a, b, 6));
+            Printf("%d\n", Memcmp(b, a, 6));
+            """,
+            stdout="0\n0\n0\n0\n0\n1\n-1\n",
+        )
+
 
 class TestCompatTools(utils.PfpTestCase):
     def setUp(self):