Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [2.10.0] - 2025-09-01

### Added

- Support for partially linked objects (a.k.a. `plf`).
- This is done by calling a function with a user provided callback that
converts a given object path into the corresponding mapfile for that `plf`.
- Rust: `MapFile::resolve_partially_linked_files()`.
- Python: `MapFile::resolvePartiallyLinkedFiles()`.
- All the CLI utilities include basic support for `plf`s.
- Pass the flag `-x .extension` or `--plf-ext .extension` to specify the
extension of the partially linked objects files that should be replaced with
a `.map` extension.
- The frontends API allow to further customize this behavior by passing a
callback like the one used by `MapFile::resolvePartiallyLinkedFiles`.

## [2.9.4] - 2025-06-02

### Changed
Expand Down Expand Up @@ -620,6 +636,7 @@ Full changes: <https://github.com/Decompollaborate/mapfile_parser/compare/702a73
- Initial release

[unreleased]: https://github.com/Decompollaborate/mapfile_parser/compare/master...develop
[2.10.0]: https://github.com/Decompollaborate/mapfile_parser/compare/2.9.4...2.10.0
[2.9.4]: https://github.com/Decompollaborate/mapfile_parser/compare/2.9.3...2.9.4
[2.9.3]: https://github.com/Decompollaborate/mapfile_parser/compare/2.9.2...2.9.3
[2.9.2]: https://github.com/Decompollaborate/mapfile_parser/compare/2.9.1...2.9.2
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[package]
name = "mapfile_parser"
version = "2.9.4"
version = "2.10.0"
edition = "2021"
rust-version = "1.74.0"
authors = ["Anghelo Carvajal <[email protected]>"]
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ If you use a `requirements.txt` file in your repository, then you can add
this library with the following line:

```txt
mapfile_parser>=2.9.4,<3.0.0
mapfile_parser>=2.10.0,<3.0.0
```

#### Development version
Expand Down Expand Up @@ -75,7 +75,7 @@ cargo add mapfile_parser
Or add the following line manually to your `Cargo.toml` file:

```toml
mapfile_parser = "2.9.4"
mapfile_parser = "2.10.0"
```

## Versioning and changelog
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[project]
name = "mapfile_parser"
version = "2.9.4"
version = "2.10.0"
description = "Map file parser library focusing decompilation projects"
readme = "README.md"
requires-python = ">=3.9"
Expand Down
2 changes: 1 addition & 1 deletion src/mapfile_parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from __future__ import annotations

__version_info__ = (2, 9, 4)
__version_info__ = (2, 10, 0)
__version__ = ".".join(map(str, __version_info__)) # + "-dev0"
__author__ = "Decompollaborate"

Expand Down
48 changes: 40 additions & 8 deletions src/mapfile_parser/frontends/bss_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,31 @@
from __future__ import annotations

import argparse
from collections.abc import Callable
import decomp_settings
from pathlib import Path

from .. import mapfile
from .. import utils


def getComparison(mapPath, expectedMapPath, *, reverseCheck: bool=True) -> mapfile.MapsComparisonInfo:
buildMap = mapfile.MapFile()
buildMap.readMapFile(mapPath)
def getComparison(
mapPath: Path,
expectedMapPath: Path,
*,
reverseCheck: bool=True,
plfResolver: Callable[[Path], Path|None]|None=None,
plfResolverExpected: Callable[[Path], Path|None]|None=None,
) -> mapfile.MapsComparisonInfo:
buildMap = mapfile.MapFile.newFromMapFile(mapPath)
buildMap = buildMap.filterBySectionType(".bss")
if plfResolver is not None:
buildMap = buildMap.resolvePartiallyLinkedFiles(plfResolver)

expectedMap = mapfile.MapFile()
expectedMap.readMapFile(expectedMapPath)
expectedMap = mapfile.MapFile.newFromMapFile(expectedMapPath)
expectedMap = expectedMap.filterBySectionType(".bss")
if plfResolverExpected is not None:
expectedMap = expectedMap.resolvePartiallyLinkedFiles(plfResolverExpected)

return buildMap.compareFilesAndSymbols(expectedMap, checkOtherOnSelf=reverseCheck)

Expand Down Expand Up @@ -126,15 +136,23 @@ def printFileComparison(comparisonInfo: mapfile.MapsComparisonInfo):
utils.eprint("Some files appear to be missing symbols. Have they been renamed or declared as static? You may need to remake 'expected'")


def doBssCheck(mapPath, expectedMapPath, *, printAll: bool=False, reverseCheck: bool=True) -> int:
def doBssCheck(
mapPath: Path,
expectedMapPath: Path,
*,
printAll: bool=False,
reverseCheck: bool=True,
plfResolver: Callable[[Path], Path|None]|None=None,
plfResolverExpected: Callable[[Path], Path|None]|None=None,
) -> int:
if not mapPath.exists():
utils.eprint(f"{mapPath} must exist")
return 1
if not expectedMapPath.exists():
utils.eprint(f"{expectedMapPath} must exist")
return 1

comparisonInfo = getComparison(mapPath, expectedMapPath, reverseCheck=reverseCheck)
comparisonInfo = getComparison(mapPath, expectedMapPath, reverseCheck=reverseCheck, plfResolver=plfResolver, plfResolverExpected=plfResolverExpected)
printSymbolComparison(comparisonInfo, printAll)

if len(comparisonInfo.badFiles) + len(comparisonInfo.missingFiles) != 0:
Expand Down Expand Up @@ -165,8 +183,20 @@ def processArguments(args: argparse.Namespace, decompConfig: decomp_settings.Con

printAll: bool = args.print_all
reverseCheck: bool = not args.no_reverse_check
plfExt: list[str]|None = args.plf_ext

exit(doBssCheck(mapPath, expectedMapPath, printAll=printAll, reverseCheck=reverseCheck))
plfResolver = None
if plfExt is not None:
def resolver(x: Path) -> Path|None:
if x.suffix in plfExt:
newPath = x.with_suffix(".map")
if newPath.exists():
return newPath
return None

plfResolver = resolver

exit(doBssCheck(mapPath, expectedMapPath, printAll=printAll, reverseCheck=reverseCheck, plfResolver=plfResolver))


def addSubparser(subparser: argparse._SubParsersAction[argparse.ArgumentParser], decompConfig: decomp_settings.Config|None=None):
Expand All @@ -193,4 +223,6 @@ def addSubparser(subparser: argparse._SubParsersAction[argparse.ArgumentParser],
parser.add_argument("-a", "--print-all", help="Print all bss, not just non-matching.", action="store_true")
parser.add_argument("--no-reverse-check", help="Disable looking for symbols on the expected map that are missing on the built map file.", action="store_true")

parser.add_argument("-x", "--plf-ext", help="File extension for partially linked files (plf). Will be used to transform the `plf`s path into a mapfile path by replacing the extension. The extension must contain the leading period. This argument can be passed multiple times.", action="append")

parser.set_defaults(func=processArguments)
45 changes: 38 additions & 7 deletions src/mapfile_parser/frontends/first_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,28 @@
from __future__ import annotations

import argparse
from collections.abc import Callable
import decomp_settings
from pathlib import Path
from typing import Callable, Literal
from typing import Literal

from .. import mapfile
from .. import utils


def doFirstDiff(mapPath, expectedMapPath, romPath, expectedRomPath, diffCount: int=5, mismatchSize: bool=False, addColons: bool=True, bytesConverterCallback:Callable[[bytes, mapfile.MapFile],str|None]|None=None, endian: Literal["big", "little"] ="big") -> int:
def doFirstDiff(
mapPath: Path,
expectedMapPath: Path,
romPath: Path,
expectedRomPath: Path,
diffCount: int=5,
mismatchSize: bool=False,
addColons: bool=True,
bytesConverterCallback: Callable[[bytes, mapfile.MapFile],str|None]|None=None,
endian: Literal["big", "little"] ="big",
plfResolver: Callable[[Path], Path|None]|None=None,
plfResolverExpected: Callable[[Path], Path|None]|None=None,
) -> int:
if not mapPath.exists():
print(f"{mapPath} must exist")
return 1
Expand All @@ -41,10 +54,13 @@ def doFirstDiff(mapPath, expectedMapPath, romPath, expectedRomPath, diffCount: i
print("No differences!")
return 0

builtMapFile = mapfile.MapFile()
builtMapFile.readMapFile(mapPath)
expectedMapFile = mapfile.MapFile()
expectedMapFile.readMapFile(expectedMapPath)
builtMapFile = mapfile.MapFile.newFromMapFile(mapPath)
if plfResolver is not None:
builtMapFile = builtMapFile.resolvePartiallyLinkedFiles(plfResolver)

expectedMapFile = mapfile.MapFile.newFromMapFile(expectedMapPath)
if plfResolverExpected is not None:
expectedMapFile = expectedMapFile.resolvePartiallyLinkedFiles(plfResolverExpected)

endian_diff = 0
if endian == "little":
Expand Down Expand Up @@ -166,7 +182,20 @@ def processArguments(args: argparse.Namespace, decompConfig: decomp_settings.Con

endian = args.endian

exit(doFirstDiff(mapPath, expectedMapPath, romPath, expectedRomPath, diffCount, mismatchSize, endian=endian))
plfExt: list[str]|None = args.plf_ext

plfResolver = None
if plfExt is not None:
def resolver(x: Path) -> Path|None:
if x.suffix in plfExt:
newPath = x.with_suffix(".map")
if newPath.exists():
return newPath
return None

plfResolver = resolver

exit(doFirstDiff(mapPath, expectedMapPath, romPath, expectedRomPath, diffCount, mismatchSize, endian=endian, plfResolver=plfResolver))


def addSubparser(subparser: argparse._SubParsersAction[argparse.ArgumentParser], decompConfig: decomp_settings.Config|None=None):
Expand Down Expand Up @@ -200,4 +229,6 @@ def addSubparser(subparser: argparse._SubParsersAction[argparse.ArgumentParser],
parser.add_argument("-m", "--mismatch-size", help="Do not exit early if the ROM sizes does not match", action="store_true")
parser.add_argument("-e", "--endian", help="Specify endianness of the binary", choices=["big", "little"], default="big")

parser.add_argument("-x", "--plf-ext", help="File extension for partially linked files (plf). Will be used to transform the `plf`s path into a mapfile path by replacing the extension. The extension must contain the leading period. This argument can be passed multiple times.", action="append")

parser.set_defaults(func=processArguments)
30 changes: 26 additions & 4 deletions src/mapfile_parser/frontends/jsonify.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,28 @@
from __future__ import annotations

import argparse
from collections.abc import Callable
import decomp_settings
import json
from pathlib import Path

from .. import mapfile


def doJsonify(mapPath: Path, outputPath: Path|None, humanReadable: bool=True, applyFixes: bool=False) -> int:
def doJsonify(
mapPath: Path,
outputPath: Path|None,
humanReadable: bool=True,
applyFixes: bool=False,
plfResolver: Callable[[Path], Path|None]|None=None,
) -> int:
if not mapPath.exists():
print(f"Could not find mapfile at '{mapPath}'")
return 1

mapFile = mapfile.MapFile()
mapFile.readMapFile(mapPath)
mapFile = mapfile.MapFile.newFromMapFile(mapPath)
if plfResolver is not None:
mapFile = mapFile.resolvePartiallyLinkedFiles(plfResolver)

jsonStr = json.dumps(mapFile.toJson(humanReadable=humanReadable), indent=4)

Expand All @@ -44,8 +52,20 @@ def processArguments(args: argparse.Namespace, decompConfig: decomp_settings.Con
outputPath: Path|None = Path(args.output) if args.output is not None else None
machine: bool = args.machine
applyFixes: bool = args.apply_fixes
plfExt: list[str]|None = args.plf_ext

exit(doJsonify(mapPath, outputPath, humanReadable=not machine, applyFixes=applyFixes))
plfResolver = None
if plfExt is not None:
def resolver(x: Path) -> Path|None:
if x.suffix in plfExt:
newPath = x.with_suffix(".map")
if newPath.exists():
return newPath
return None

plfResolver = resolver

exit(doJsonify(mapPath, outputPath, humanReadable=not machine, applyFixes=applyFixes, plfResolver=plfResolver))

def addSubparser(subparser: argparse._SubParsersAction[argparse.ArgumentParser], decompConfig: decomp_settings.Config|None=None):
parser = subparser.add_parser("jsonify", help="Converts a mapfile into a json format.")
Expand All @@ -67,4 +87,6 @@ def addSubparser(subparser: argparse._SubParsersAction[argparse.ArgumentParser],
parser.add_argument("-m", "--machine", help="Emit numbers as numbers instead of outputting them as pretty strings.", action="store_true")
parser.add_argument("-f", "--apply-fixes", help="DEPRECATED, this is applied automatically now. Apply certain fixups, like fixing size calculation of because of the existence of fake `.NON_MATCHING` symbols.", action="store_true")

parser.add_argument("-x", "--plf-ext", help="File extension for partially linked files (plf). Will be used to transform the `plf`s path into a mapfile path by replacing the extension. The extension must contain the leading period. This argument can be passed multiple times.", action="append")

parser.set_defaults(func=processArguments)
22 changes: 20 additions & 2 deletions src/mapfile_parser/frontends/objdiff_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from __future__ import annotations

import argparse
from collections.abc import Callable
import dataclasses
import decomp_settings
from pathlib import Path
Expand Down Expand Up @@ -34,13 +35,15 @@ def doObjdiffReport(
emitCategories: bool=False,
quiet: bool=False,
summaryTableConfig: SummaryTableConfig|None=SummaryTableConfig(),
plfResolver: Callable[[Path], Path|None]|None=None,
) -> int:
if not mapPath.exists():
print(f"Could not find mapfile at '{mapPath}'")
return 1

mapFile = mapfile.MapFile()
mapFile.readMapFile(mapPath)
mapFile = mapfile.MapFile.newFromMapFile(mapPath)
if plfResolver is not None:
mapFile = mapFile.resolvePartiallyLinkedFiles(plfResolver)

if emitCategories:
printDefaultCategories(mapFile, prefixesToTrim)
Expand Down Expand Up @@ -347,6 +350,18 @@ def processArguments(args: argparse.Namespace, decompConfig: decomp_settings.Con
else:
summaryTableConfig = None

plfExt: list[str]|None = args.plf_ext

plfResolver = None
if plfExt is not None:
def resolver(x: Path) -> Path|None:
if x.suffix in plfExt:
newPath = x.with_suffix(".map")
if newPath.exists():
return newPath
return None
plfResolver = resolver

exit(doObjdiffReport(
mapPath,
outputPath,
Expand All @@ -357,6 +372,7 @@ def processArguments(args: argparse.Namespace, decompConfig: decomp_settings.Con
nonmatchingsPath=nonmatchingsPath,
emitCategories=emitCategories,
summaryTableConfig=summaryTableConfig,
plfResolver=plfResolver,
))

def addSubparser(subparser: argparse._SubParsersAction[argparse.ArgumentParser], decompConfig: decomp_settings.Config|None=None):
Expand Down Expand Up @@ -468,6 +484,8 @@ def addSubparser(subparser: argparse._SubParsersAction[argparse.ArgumentParser],
parser.add_argument("--emit-categories", help="Print automatically-generated categories from your mapfile, using the decomp.yaml format. These categories are expected to be tweaked and not used as-is.", action="store_true")
parser.add_argument("--quiet", help="Avoid printing the progress report to the stdout and to the Github action summary.", action="store_true")

parser.add_argument("-x", "--plf-ext", help="File extension for partially linked files (plf). Will be used to transform the `plf`s path into a mapfile path by replacing the extension. The extension must contain the leading period. This argument can be passed multiple times.", action="append")

parser.set_defaults(func=processArguments)


Expand Down
Loading
Loading