happyleavesaoc / aoc-mgz

Age of Empires II recorded game parsing and summarization in Python 3.
MIT License
197 stars 41 forks source link

struct.error: unpack requires a buffer of 4 bytes #91

Closed Namek closed 1 year ago

Namek commented 1 year ago

Hi, so the title is for one error but actually there are multiple errors in the chain:

The full trace stack:

Traceback (most recent call last):
  File "w:\dev\ant_league\backend\mgz\mgz\fast\header.py", line 517, in parse
    de = parse_de(header, version, save)
  File "w:\dev\ant_league\backend\mgz\mgz\fast\header.py", line 293, in parse_de
    difficulty_id = unpack('<I', data)
  File "w:\dev\ant_league\backend\mgz\mgz\util.py", line 339, in unpack
    output = struct.unpack(fmt, data.read(struct.calcsize(fmt)))
struct.error: unpack requires a buffer of 4 bytes

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "w:\dev\ant_league\backend\.\src\recordings.py", line 16, in get_match_info
    m = parse_match(data)
  File "w:\dev\ant_league\backend\mgz\mgz\model\__init__.py", line 105, in parse_match
    data = parse(handle)
  File "w:\dev\ant_league\backend\mgz\mgz\fast\header.py", line 525, in parse
    raise RuntimeError(f"could not parse: {e}")
RuntimeError: could not parse: unpack requires a buffer of 4 bytes

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 1042, in _parse
    obj.append(self.subcon._parse(stream, context._, path))
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 867, in _parse
    subobj = sc._parse(stream, context, path)
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 1594, in _parse
    obj = self.cases.get(key, self.default)._parse(stream, context, path)
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 1235, in _parse
    pad = _read_stream(stream, padlen)
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 69, in _read_stream
    raise FieldError("could not read enough bytes, expected %d, found %d" % (length, len(data)))
construct.core.FieldError: could not read enough bytes, expected 214224078075, found 3374978

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 2784, in _parse
    return self.subcon._parse(stream, context, path)
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 867, in _parse
    subobj = sc._parse(stream, context, path)
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 1050, in _parse
    raise RangeError("expected %d to %d, found %d" % (min, max, len(obj)))
construct.core.RangeError: expected 131072 to 131072, found 0

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "w:\dev\ant_league\backend\mgz\mgz\summary\full.py", line 75, in __init__
    self._header = mgz.header.parse_stream(self._handle)
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 171, in parse_stream
    return self._parse(stream, context2, "(parsing)")
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 867, in _parse
    subobj = sc._parse(stream, context, path)
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 295, in _parse
    return self.subcon._parse(stream, context, path)
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 867, in _parse
    subobj = sc._parse(stream, context, path)
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 295, in _parse
    return self.subcon._parse(stream, context, path)
  File "w:\dev\ant_league\backend\mgz\mgz\util.py", line 58, in _parse
    return self.subcon._parse(new_stream, context, path)
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 357, in _parse
    return self.subcon.parse(data, context)
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 158, in parse
    return self.parse_stream(BytesIO(data), context, **kw)
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 171, in parse_stream
    return self._parse(stream, context2, "(parsing)")
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 867, in _parse
    subobj = sc._parse(stream, context, path)
  File "w:\dev\ant_league\backend\lib\site-packages\construct\core.py", line 2788, in _parse
    raise e.__class__("%s\n    %s" % (e, path))
construct.core.RangeError: expected 131072 to 131072, found 0
    (parsing) -> map_info

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "w:\dev\ant_league\backend\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 369, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "w:\dev\ant_league\backend\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 59, in __call__
    return await self.app(scope, receive, send)
  File "w:\dev\ant_league\backend\lib\site-packages\fastapi\applications.py", line 212, in __call__
    await super().__call__(scope, receive, send)
  File "w:\dev\ant_league\backend\lib\site-packages\starlette\applications.py", line 112, in __call__
    await self.middleware_stack(scope, receive, send)
  File "w:\dev\ant_league\backend\lib\site-packages\starlette\middleware\errors.py", line 181, in __call__
    raise exc
  File "w:\dev\ant_league\backend\lib\site-packages\starlette\middleware\errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "w:\dev\ant_league\backend\lib\site-packages\starlette\middleware\cors.py", line 92, in __call__
    await self.simple_response(scope, receive, send, request_headers=headers)
  File "w:\dev\ant_league\backend\lib\site-packages\starlette\middleware\cors.py", line 147, in simple_response
    await self.app(scope, receive, send)
  File "w:\dev\ant_league\backend\lib\site-packages\starlette\middleware\sessions.py", line 77, in __call__
    await self.app(scope, receive, send_wrapper)
  File "w:\dev\ant_league\backend\lib\site-packages\starlette\exceptions.py", line 82, in __call__
    raise exc
  File "w:\dev\ant_league\backend\lib\site-packages\starlette\exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "w:\dev\ant_league\backend\lib\site-packages\starlette\routing.py", line 656, in __call__
    await route.handle(scope, receive, send)
  File "w:\dev\ant_league\backend\lib\site-packages\starlette\routing.py", line 259, in handle
    await self.app(scope, receive, send)
  File "w:\dev\ant_league\backend\lib\site-packages\starlette\routing.py", line 61, in app
    response = await func(request)
  File "w:\dev\ant_league\backend\lib\site-packages\fastapi\routing.py", line 226, in app
    raw_response = await run_endpoint_function(
  File "w:\dev\ant_league\backend\lib\site-packages\fastapi\routing.py", line 161, in run_endpoint_function
    return await run_in_threadpool(dependant.call, **values)
  File "w:\dev\ant_league\backend\lib\site-packages\starlette\concurrency.py", line 39, in run_in_threadpool
    return await anyio.to_thread.run_sync(func, *args)
  File "w:\dev\ant_league\backend\lib\site-packages\anyio\to_thread.py", line 28, in run_sync
    return await get_asynclib().run_sync_in_worker_thread(func, *args, cancellable=cancellable,
  File "w:\dev\ant_league\backend\lib\site-packages\anyio\_backends\_asyncio.py", line 818, in run_sync_in_worker_thread
    return await future
  File "w:\dev\ant_league\backend\lib\site-packages\anyio\_backends\_asyncio.py", line 754, in run
    result = context.run(func, *args)
  File "w:\dev\ant_league\backend\.\src\website.py", line 164, in post_match
    match_info = get_match_info(file.file)
  File "w:\dev\ant_league\backend\.\src\recordings.py", line 33, in get_match_info
    s = Summary(data)
  File "w:\dev\ant_league\backend\mgz\mgz\summary\__init__.py", line 27, in __call__
    return FullSummary(data)
  File "w:\dev\ant_league\backend\mgz\mgz\summary\full.py", line 87, in __init__
    raise RuntimeError("invalid mgz file: {}".format(e))
RuntimeError: invalid mgz file: expected 131072 to 131072, found 0
    (parsing) -> map_info

where the recordings.py has the get_match_info function that tries the parse_match first, then on error tries the Summary method:

from pathlib import Path
from re import A
from sqlalchemy import delete, join, select, update

from mgz.model import parse_match
from mgz.summary import Summary

from . import cfg
from .database import get_db
from .models import Recording, AssocRecordingsPlayers

def get_match_info(data):
    try:
        # some files seems broken for this library when using the `Summary`.
        m = parse_match(data)
        players = [dict(name=p.name, user_id=p.profile_id, number=p.number,
                        civilization=p.civilization) for p in m.players]

        return dict(
            map_name=m.map.name,
            game_version=f"{m.version.name} {m.version.value}",
            game_map_type=m.type,
            players=players,
            teams=m.teams,
            completed=False,
            start_time_seconds=int(m.actions[0].timestamp.seconds),
            duration_seconds=m.duration.seconds,
        )
    except RuntimeError:
        # the `parse_match` method doesn't work for restored recordings, thus, let's try with the `Summary`.
        data.seek(0)
        s = Summary(data)

        return dict(
            map_name=s.get_map()['name'],
            game_version=" ".join(str(x) for x in s.get_version()),
            game_map_type=s.get_settings()['type'][1],
            players=s.get_players(),
            teams=s.get_teams(),
            completed=s.get_completed(),
            start_time_seconds=int(s.get_start_time()/1000),
            duration_seconds=int(s.get_duration()/1000),
        )

Here are 2 files to test this: records.zip

happyleavesaoc commented 1 year ago

Fixed in https://github.com/happyleavesaoc/aoc-mgz/commit/dd4d122259e3b97fe09ac08aa75fe7ff5a75c72d