From 83180637296bd23bbdeb8ff1f0f47c21c91bb251 Mon Sep 17 00:00:00 2001 From: 1superchip <1superchip@gmail.com> Date: Thu, 8 Oct 2026 21:34:53 -0500 Subject: [PATCH] Clean up and document misc. functions in Collision.cpp and tools.cpp. Add Camera enums. Push check_symbols.py --- include/common/Camera.h | 14 +- include/ty/tools.h | 7 +- src/common/Source/Camera.cpp | 30 +-- src/common/Source/Collision.cpp | 108 ++++++--- src/ty/source/tools.cpp | 56 +++-- tools/check_symbols.py | 390 ++++++++++++++++++++++++++++++++ 6 files changed, 526 insertions(+), 79 deletions(-) create mode 100644 tools/check_symbols.py diff --git a/include/common/Camera.h b/include/common/Camera.h index ee8ceca..6c8ca25 100644 --- a/include/common/Camera.h +++ b/include/common/Camera.h @@ -5,13 +5,17 @@ #include "common/Vector.h" enum Camera_Mode { - CM_FIRST = 0, - // - CM_COUNT = 3, + CM_FIRST = 0, + CM_0 = 0, + CM_1 = 1, + CM_2 = 2, + CM_COUNT = 3, }; enum Camera_Direction { - + CD_0 = 0, + CD_1 = 1, + CD_2 = 2, }; struct KeyMap { @@ -40,7 +44,7 @@ struct Camera { bool Update(InputDevices device, Vector*); Camera_Mode mode; - int unk4; + Camera_Direction direction; KeyMap* pMap; float unkC; Vector pos; diff --git a/include/ty/tools.h b/include/ty/tools.h index 214125b..ec16c80 100644 --- a/include/ty/tools.h +++ b/include/ty/tools.h @@ -242,8 +242,7 @@ Vector* Tools_GetRefPointPos(Model* pModel, char*); void Tools_ApplyFrictionAndGravity(Vector*, Vector*, Vector*, float); -// Tools_RandomRadialVectorXZ__FR6Vector -void Tools_RandomRadialVectorXZ(Vector&); // might be the wrong signature +void Tools_RandomRadialVectorXZ(Vector&); Vector* Tools_RandomNormal(Vector*); float Tools_TurnToAngle(float currentAngle, float maxAngle, float maxTurnAngle); @@ -266,8 +265,8 @@ struct Tools_DynamicStringTable { void Tools_EnableWideScreen(View* pView, bool bEnableWideScreen); -float Tools_CylinderTest(Vector*, Vector*, float radius, Vector* pTestPoint); -bool Tools_CapsuleTest(Vector*, Vector*, float, float, Vector*); +float Tools_CylinderTest(Vector* pStart, Vector* pEnd, float radius, Vector* pTestPoint); +bool Tools_CapsuleTest(Vector* pStart, Vector* pEnd, float capsuleRadius, float cylinderRadius, Vector* pTestPoint); #define NUM_PADKEYS 16 struct PadKey { diff --git a/src/common/Source/Camera.cpp b/src/common/Source/Camera.cpp index d49dcf8..09bfa4c 100644 --- a/src/common/Source/Camera.cpp +++ b/src/common/Source/Camera.cpp @@ -73,8 +73,8 @@ void Camera::DeinitModule(void) { void Camera::Init(void) { pMap = keyMapDS; - mode = (Camera_Mode)0; - unk4 = 2; + mode = CM_0; + direction = CD_2; unkC = 1.0f; pos.Set(0.0f, 0.0f, -10.0f); @@ -104,7 +104,7 @@ void Camera::Reposition(Camera_Mode camMode, Vector* pPosition, Vector* pTarget) #endif // defined(BARBIE_ICESKATING) && BARBIE_ICESKATING == 1 mode = camMode; - unk4 = (Camera_Direction)2; + direction = CD_2; pos = *pPosition; target = *pTarget; @@ -115,7 +115,7 @@ void Camera::Reposition(Camera_Mode camMode, Vector* pPosition, Vector* pTarget) } void Camera::SetMode(Camera_Mode camMode) { - if (mode != (int)camMode) { + if (mode != camMode) { mode = camMode; } } @@ -123,8 +123,8 @@ void Camera::SetMode(Camera_Mode camMode) { void Camera::SetFixedDir(Camera_Direction camDir) { unk40.w = 1.0f; - if (unk4 != (int)camDir) { - unk4 = camDir; + if (direction != camDir) { + direction = camDir; } } @@ -220,12 +220,12 @@ bool Camera::Update(InputDevices device, Vector* p) { SetMode((Camera_Mode)(mode + 1)); } - if (p == NULL && mode != 0) { - SetMode((Camera_Mode)0); + if (p == NULL && mode != CM_0) { + SetMode(CM_0); } if (bChangeDir) { - SetFixedDir((Camera_Direction)(unk4 + 1)); + SetFixedDir((Camera_Direction)(direction + 1)); } if (unk40.w == 1.0f && (Abs(f27) > 1e-06f || Abs(f22) > 1e-06f)) { @@ -241,7 +241,7 @@ bool Camera::Update(InputDevices device, Vector* p) { float mag; switch (mode) { - case 0: + case CM_0: ySignMod = 1.0f; if (Abs(f23) > 1e-06f) { tempv.Set(0.0f, f23 * 0.2f, 0.0f); @@ -263,7 +263,7 @@ bool Camera::Update(InputDevices device, Vector* p) { pos.Add(&tempv); } break; - case 1: + case CM_1: if (bChangeMode) { tempv.Sub(p, &pos); dir.w = tempv.Magnitude(); @@ -293,7 +293,7 @@ bool Camera::Update(InputDevices device, Vector* p) { target = *p; } break; - case 2: + case CM_2: if (bChangeMode) { tempv.Sub(p, &pos); dir.w = tempv.Magnitude(); @@ -401,11 +401,11 @@ bool Camera::Update(InputDevices device, Vector* p) { } switch (mode) { - case 0: + case CM_0: target.Add(&pos, &dir); break; - case 1: - case 2: + case CM_1: + case CM_2: tempv.Sub(&pos, &target); mag = tempv.Magnitude(); if (mag - dir.w > scaledSpeed * 0.2f) { diff --git a/src/common/Source/Collision.cpp b/src/common/Source/Collision.cpp index 6e0d465..6c22724 100644 --- a/src/common/Source/Collision.cpp +++ b/src/common/Source/Collision.cpp @@ -310,21 +310,37 @@ static void NearestPointOnPolyEdge(Vector* pVec, Vector* pVec1, int* indices, in } } -float CylTest_CapsFirst(Vector* pVec, Vector* pVec1, float param_3, float param_4, Vector* pVec2) { - float x0 = pVec1->x - pVec->x; - float y0 = pVec1->y - pVec->y; - float z0 = pVec1->z - pVec->z; - float x1 = pVec2->x - pVec->x; - float y1 = pVec2->y - pVec->y; - float z1 = pVec2->z - pVec->z; - float local_3C = (x1 * x0) + (y1 * y0) + (z1 * z0); - if (local_3C < 0.0f || local_3C > param_3) { +/// @brief +/// @param pStart Start point the cylinder axis (A). +/// @param pEnd End point the cylinder axis B). +/// @param axisLenSq Squared length of the axis segment, |B - A|^2. +/// The caller must precompute this (e.g. v.MagSquared()). +/// Must be > 0, since it is used as a divisor. +/// @param param_4 +/// @param pPoint The point to test (P). +/// @return +float CylTest_CapsFirst(Vector* pStart, Vector* pEnd, float axisLenSq, float param_4, Vector* pPoint) { + float x0 = pEnd->x - pStart->x; + float y0 = pEnd->y - pStart->y; + float z0 = pEnd->z - pStart->z; + + float x1 = pPoint->x - pStart->x; + float y1 = pPoint->y - pStart->y; + float z1 = pPoint->z - pStart->z; + + float projDot = (x1 * x0) + (y1 * y0) + (z1 * z0); + + // Check if projection lies between A and B + if (projDot < 0.0f || projDot > axisLenSq) { return -1.0f; } - float t = (x1 * x1) + (y1 * y1) + (z1 * z1) - (local_3C * local_3C) / (param_3); + + float t = (x1 * x1) + (y1 * y1) + (z1 * z1) - ((projDot * projDot) / axisLenSq); + if (t > param_4) { return -1.0f; } + return t; } @@ -354,10 +370,20 @@ static bool PointInPoly(Vector* pVec, Vector* pVec1, Vector* pVec2, int* indices return !IsPointOnFrontSide(pVec, pVec1, &pVec2[indices[nmbrOfVertices - 1]], &pVec2[indices[0]]); } -float SubDot(Vector* vec, Vector* vec1, Vector* vec2) { - Vector tmp; - tmp.Sub(vec, vec1); - return tmp.Dot(vec2); +/// @brief Computes the signed distance from a point to a plane, defined by a point on +/// the plane and the plane's normal: (pPoint - pPlanePoint) . pPlaneNormal +/// +/// The result is positive when pPoint is on the side the normal faces, negative +/// on the opposite side, and zero when pPoint lies on the plane. +/// +/// @param pPoint The point being tested. +/// @param pPlanePoint Any point on the plane. +/// @param pPlaneNormal The plane's normal (expected to be normalized). +/// @return Signed distance from the plane to pPoint. +float SignedDistanceToPlane(Vector* pPoint, Vector* pPlanePoint, Vector* pPlaneNormal) { + Vector v; + v.Sub(pPoint, pPlanePoint); + return v.Dot(pPlaneNormal); } static bool SweepSphereToPoly(SphereRay* pRay, Vector* pVectors, int* indices, int numVerts, Vector* arg5, Vector* arg6, CollisionResult* pResult) { @@ -369,7 +395,7 @@ static bool SweepSphereToPoly(SphereRay* pRay, Vector* pVectors, int* indices, i return false; } - float d = SubDot(&pRay->mStart, &pVectors[indices[0]], arg5); + float d = SignedDistanceToPlane(&pRay->mStart, &pVectors[indices[0]], arg5); if (d > pRay->mLength + pRay->radius) { return false; } @@ -446,7 +472,7 @@ static bool SweepSphereToTri(SphereRay* pRay, Vector* pVert0, Vector* pVert1, return false; } - float d = SubDot(&pRay->mStart, pVert0, pVec3); + float d = SignedDistanceToPlane(&pRay->mStart, pVert0, pVec3); if (d > pRay->mLength + pRay->radius) { return false; } @@ -586,8 +612,13 @@ int Collision_SphereCollide(Vector* pPos, float radius, CollisionResult* pCr, in Vector* triVert0 = (Vector*)&pItem->collisionThing->verts[0]; Vector* triVert1 = (Vector*)&pItem->collisionThing->verts[1]; Vector* triVert2 = (Vector*)&pItem->collisionThing->verts[2]; - float fVar19 = - SubDot(pPos, (Vector*)&pItem->collisionThing->verts[0], (Vector*)&pItem->collisionThing->normal); + + float fVar19 = SignedDistanceToPlane( + pPos, + (Vector*)&pItem->collisionThing->verts[0], + (Vector*)&pItem->collisionThing->normal + ); + float fVar0 = Abs(fVar19); if (!(fVar0 > radius)) { Vector v1; @@ -728,21 +759,32 @@ bool InFrontOfItem(Vector* pPoint, Item* pItem) { v.z * pItem->collisionThing->normal[2] >= 0.0f; } -bool CheckItemSphereRay(Item* pItem, Vector* pVec, Vector* pVec1) { - CollisionThing* pThing = pItem->collisionThing; - bool ret = false; - if ((((pThing->verts[0].pos[1] < pVec->y && pThing->verts[1].pos[1] < pVec->y && - pItem->collisionThing->verts[2].pos[1] < pVec->y) || (pThing->verts[0].pos[1] > pVec1->y && - pItem->collisionThing->verts[1].pos[1] > pVec1->y && pThing->verts[2].pos[1] > pVec1->y)) || - ((pItem->collisionThing->verts[0].pos[0] < pVec->x && pThing->verts[1].pos[0] < pVec->x && - pItem->collisionThing->verts[2].pos[0] < pVec->x) || (pThing->verts[0].pos[0] > pVec1->x && - pItem->collisionThing->verts[1].pos[0] > pVec1->x && pThing->verts[2].pos[0] > pVec1->x)) || - ((pItem->collisionThing->verts[0].pos[2] < pVec->z && pThing->verts[1].pos[2] < pVec->z && - pItem->collisionThing->verts[2].pos[2] < pVec->z) || (pThing->verts[0].pos[2] > pVec1->z && - pItem->collisionThing->verts[1].pos[2] > pVec1->z && pThing->verts[2].pos[2] > pVec1->z)))) { - ret = true; - } - return ret; +bool CheckItemSphereRay(Item* pItem, Vector* pRayMinPos, Vector* pRayMaxPos) { + return ( + pItem->collisionThing->verts[0].pos[1] < pRayMinPos->y && + pItem->collisionThing->verts[1].pos[1] < pRayMinPos->y && + pItem->collisionThing->verts[2].pos[1] < pRayMinPos->y + ) || ( + pItem->collisionThing->verts[0].pos[1] > pRayMaxPos->y && + pItem->collisionThing->verts[1].pos[1] > pRayMaxPos->y && + pItem->collisionThing->verts[2].pos[1] > pRayMaxPos->y + ) || ( + pItem->collisionThing->verts[0].pos[0] < pRayMinPos->x && + pItem->collisionThing->verts[1].pos[0] < pRayMinPos->x && + pItem->collisionThing->verts[2].pos[0] < pRayMinPos->x + ) || ( + pItem->collisionThing->verts[0].pos[0] > pRayMaxPos->x && + pItem->collisionThing->verts[1].pos[0] > pRayMaxPos->x && + pItem->collisionThing->verts[2].pos[0] > pRayMaxPos->x + ) || ( + pItem->collisionThing->verts[0].pos[2] < pRayMinPos->z && + pItem->collisionThing->verts[1].pos[2] < pRayMinPos->z && + pItem->collisionThing->verts[2].pos[2] < pRayMinPos->z + ) || ( + pItem->collisionThing->verts[0].pos[2] > pRayMaxPos->z && + pItem->collisionThing->verts[1].pos[2] > pRayMaxPos->z && + pItem->collisionThing->verts[2].pos[2] > pRayMaxPos->z + ); } /// @brief Tests if a collision occurs against a poly from the ray formed by vectors pStart and pEnd diff --git a/src/ty/source/tools.cpp b/src/ty/source/tools.cpp index f0c9675..11adf07 100644 --- a/src/ty/source/tools.cpp +++ b/src/ty/source/tools.cpp @@ -669,8 +669,13 @@ bool Tools_TestFloor(Vector* pt, CollisionResult* pCr, float f1, bool bCollision CollisionResult localCR; pCr = pCr ? pCr : &localCR; Vector end = {pt->x, pt->y - f1, pt->z}; - return Collision_RayCollide(pt, &end, pCr, - bCollisionMode ? COLLISION_MODE_ALL : COLLISION_MODE_POLY, 0); + + return Collision_RayCollide( + pt, &end, + pCr, + bCollisionMode ? COLLISION_MODE_ALL : COLLISION_MODE_POLY, + 0 + ); } // Returns the y coordinate of the floor under pPos @@ -679,10 +684,16 @@ float Tools_GetFloor(const Vector& pPos, CollisionResult* pCr, float maxRayDist, CollisionResult localCR; pCr = pCr ? pCr : &localCR; Vector end = {pPos.x, pPos.y - maxRayDist, pPos.z}; - if (Collision_RayCollide((Vector*)&pPos, &end, pCr, - bCollisionMode ? COLLISION_MODE_ALL : COLLISION_MODE_POLY, collisionFlags)) { + + if (Collision_RayCollide( + (Vector*)&pPos, &end, + pCr, + bCollisionMode ? COLLISION_MODE_ALL : COLLISION_MODE_POLY, + collisionFlags + )) { return pCr->pos.y; } + return pPos.y; } @@ -1515,34 +1526,35 @@ bool Tools_SideOfLine2D(Vector* pVec, Vector* pVec1, Vector* pVec2) { // return f0; // } -// pVec is the bottom of the cylinder? -// pVec1 is the top of the cylinder? -float Tools_CylinderTest(Vector* pVec, Vector* pVec1, float radius, Vector* pTestPoint) { +float Tools_CylinderTest(Vector* pStart, Vector* pEnd, float radius, Vector* pTestPoint) { float radSq = Sqr(radius); - Vector tmp; // difference between pVec1 and pVec (pVec1 - pVec) - Vector tmp2; - tmp.Sub(pVec1, pVec); - tmp2.Sub(pTestPoint, pVec); - float cylinderEndDist = tmp.MagSquared(); // distance between the 2 cylinder ends (pVec1, pVec) - float f1_ = tmp.Dot(&tmp2); + + Vector tmp; // pEnd - pStart + Vector tmp2; // pTestPoint - pStart + + tmp.Sub(pEnd, pStart); + tmp2.Sub(pTestPoint, pStart); + + float axisLenSq = tmp.MagSquared(); + float proj = tmp.Dot(&tmp2); - if (f1_ < 0.0f || f1_ > cylinderEndDist) { - // cylinder end test? - return -1.0f; + if (proj < 0.0f || proj > axisLenSq) { + return -1.0f; // outside the end planes } - float f0 = tmp2.MagSquared() - ((f1_ * f1_) / cylinderEndDist); - if (f0 > radSq) { + float distSq = tmp2.MagSquared() - ((proj * proj) / axisLenSq); + if (distSq > radSq) { return -1.0f; } - return f0; + return distSq; } -bool Tools_CapsuleTest(Vector* pVec, Vector* pVec1, float f1, float f2, Vector* pVec2) { - return pVec->IsInsideSphere(pVec2, f1) || pVec1->IsInsideSphere(pVec2, f1) || - Tools_CylinderTest(pVec, pVec1, f2, pVec2) >= 0.0f; +bool Tools_CapsuleTest(Vector* pStart, Vector* pEnd, float capsuleRadius, float cylinderRadius, Vector* pTestPoint) { + return pStart->IsInsideSphere(pTestPoint, capsuleRadius) || + pEnd->IsInsideSphere(pTestPoint, capsuleRadius) || + Tools_CylinderTest(pStart, pEnd, cylinderRadius, pTestPoint) >= 0.0f; } // should this be defined here? diff --git a/tools/check_symbols.py b/tools/check_symbols.py new file mode 100644 index 0000000..f8c845a --- /dev/null +++ b/tools/check_symbols.py @@ -0,0 +1,390 @@ + +from pathlib import Path + +from typing import Dict, List, Tuple + +from collections import Counter + +from tools.project import ( + Object, + ProjectConfig +) + +import re +import os + +from itertools import zip_longest + +script_dir = os.path.dirname(os.path.realpath(__file__)) +root_dir = os.path.abspath(os.path.join(script_dir, "..")) + +ORIG_ELF = os.path.join(root_dir, "orig", "GTYE69", "files", "TY_REL.elf") + +# This file was generated by a custom pyelftools readelf.py in order to include entire symbol name +# and symbol sizes as decimal rather than hex +ORIG_ELF_SYMBOLS = os.path.join(root_dir, "orig", "GTYE69", "extracted_map", "TY_REL.map.txt") + +BINUTILS_PATH = os.path.join(root_dir, "build", "binutils") + +USE_BINUTILS = os.path.exists(BINUTILS_PATH) + +if not os.path.exists(BINUTILS_PATH): + pass + # print(f"Cannot check symbols! Binutils: {BINUTILS_PATH} does not exist.") + # exit(1) + +# nm to get full symbol name as readelf limits after so many characters +NM = os.environ.get("NM", os.path.join(BINUTILS_PATH, "powerpc-eabi-nm.exe")) +READELF = os.environ.get("READELF", os.path.join(BINUTILS_PATH, "powerpc-eabi-readelf.exe")) + +if not os.path.exists(ORIG_ELF_SYMBOLS): + print(f"Cannot check_symbols! {ORIG_ELF_SYMBOLS} does not exist.") + exit(1) + +# Use a custom readelf.py in order to get all characters in a symbol (limits in order to match readelf) +CUSTOM_READELF_PY_PATH = "" +if not os.path.exists(CUSTOM_READELF_PY_PATH): + print(f"Cannot check_symbols! {CUSTOM_READELF_PY_PATH} does not exist.") + exit(1) + +HANDLE_ANON_STATIC_NAMES = True + +def compare_sym_names(lhs: str, rhs: str) -> bool: + if HANDLE_ANON_STATIC_NAMES: + ANON_LITERAL = r"@(\d+)" + + FUNCTION_STATIC = r"(\S+)\$(\d+)" + + lhs_match = re.match(ANON_LITERAL, lhs) + rhs_match = re.match(ANON_LITERAL, rhs) + + # group 0 is the full string, group 1 would be \d+ + is_lhs_anon_lit = lhs_match and len(lhs_match.groups()) > 1 + is_rhs_anon_lit = rhs_match and len(rhs_match.groups()) > 1 + + # if both are anon literals, names match + # if one or the other but not both are anon literals, names do not match + if is_lhs_anon_lit and is_rhs_anon_lit: + return True + elif is_lhs_anon_lit and (not is_rhs_anon_lit): + return False + elif (not is_lhs_anon_lit) and is_rhs_anon_lit: + return False + + lhs_match = re.match(FUNCTION_STATIC, lhs) + rhs_match = re.match(FUNCTION_STATIC, rhs) + + # group 0 is the full string, group 1 would be \d+ + is_lhs_func_static = lhs_match and len(lhs_match.groups()) > 1 + is_rhs_func_static = rhs_match and len(rhs_match.groups()) > 1 + + if is_lhs_func_static and is_rhs_func_static: + # if both are func statics, need to check the name + return re.sub(FUNCTION_STATIC, r"\1\$", lhs) == re.sub(FUNCTION_STATIC, r"\1\$", rhs) + + return lhs == rhs + +class ParsedLine: + def __init__(self, text: str, parsed_nm_info: str|None = None): + LINE_RE = r"^\s+(\d+):\s+([0-9a-fA-F]+)\s+(\d+|(?:0x)?[0-9a-fA-F]+)\s+(NOTYPE|FUNC|FILE|SECTION|OBJECT)\s+(LOCAL|GLOBAL)\s+(DEFAULT)\s+(\d+|UND|ABS)\s+(\S+)$" + + matches = re.match(LINE_RE, text) + if not matches: + self.valid = False + return + + self.valid = True + self.num = matches.group(1) + self.addr = matches.group(2) + self.size = matches.group(3) + self.type = matches.group(4) + self.bind = matches.group(5) + self.vis = matches.group(6) + self.index = matches.group(7) + + if parsed_nm_info: + self.name = parsed_nm_info[self.addr] + else: + self.name = matches.group(8) + + return + + def __eq__(self, other): + name_cond = self.name == other.name + return (1 and + # self.num == other.num and + self.addr == other.addr and + self.size == other.size and + self.type == other.type and + self.bind == other.bind and + self.vis == other.vis and + self.index == other.index and + ((self.type == "OBJECT" and other.type == "OBJECT") or compare_sym_names(self.name, other.name)) + ) + + def __str__(self): + """Returns a human-readable string representation of the parsed line.""" + if not self.valid: + return "" + + return (f"Line {self.num}: {self.name} " + f"[Addr: {self.addr}, Size: {self.size}, " + f"Type: {self.type}, Bind: {self.bind}, Index: {self.index}]") + + def __repr__(self): + """Used when the object is inside a container like a list.""" + if not self.valid: + return "" + return f"" + +class SymbolFile: + def __init__(self, line: ParsedLine): + self.line = line + + self.addr = line.addr + self.name = line.name + + self.sections: List[ParsedLine] = [] + self.symbols: List[ParsedLine] = [] + return + + def addSection(self, line: ParsedLine): + self.sections.append(line) + + def addSymbol(self, line: ParsedLine): + self.symbols.append(line) + + def __str__(self): + """Returns a formatted overview of the file, its sections, and symbols.""" + output = [f"File: {self.name} (Address: {self.addr})"] + + output.append(f" Sections ({len(self.sections)}):") + for sec in self.sections: + output.append(f" - {sec}") + + output.append(f" Symbols ({len(self.symbols)}):") + for sym in self.symbols: + output.append(f" - {sym}") + + return "\n".join(output) + + def __repr__(self): + """Used when the object is inside a container like a list.""" + return f"" + +def count_sections_by_name(parsed_lines: List[ParsedLine]) -> Counter: + """Counts occurrence of section names across a list of ParsedLine objects.""" + return Counter( + line.name + for line in parsed_lines + if line.valid and line.type == "SECTION" + ) + +def clean_name(name: str) -> str: + """Strips @ $ followed by digits from the end of a symbol name.""" + return re.sub(r"[\$@]\d+", "", name) + +ONLY_SHOW_DIFFS = True + +def is_out_of_order(reference: List[ParsedLine], target: List[ParsedLine]) -> bool: + ref_sequence = [(clean_name(line.name), line.type, line.addr) for line in reference if line.valid] + target_sequence = [(clean_name(line.name), line.type, line.addr) for line in target if line.valid] + + return ref_sequence != target_sequence + +class DiffFileResult: + def __init__(self, orig, built): + self.orig_file: SymbolFile = orig + self.built_file: SymbolFile = built + + self.differing_sections: List[Tuple[ParsedLine, ParsedLine]] = [] + + for orig_sect, build_sect in zip_longest(orig.sections, built.sections, fillvalue=None): + if not build_sect or not (orig_sect == build_sect): + self.differing_sections.append((orig_sect, build_sect)) + + self.differing_syms: List[ParsedLine] = [] + + for orig_sym, build_sym in zip_longest(orig.symbols, built.symbols, fillvalue=None): + if not build_sym or not (orig_sym == build_sym): + self.differing_syms.append((orig_sym, build_sym)) + + print(f"Diffing {orig.name} and {built.name}") + + print("Sections:") + for section_diff in self.differing_sections: + print(f" {section_diff[0]}, {section_diff[1]}") + + print("Symbols:") + for symbol_diff in self.differing_syms: + print(f" {symbol_diff[0]}, {symbol_diff[1]}") + + def has_diffs(self) -> bool: + return len(self.differing_sections) > 0 or len(self.differing_syms) > 0 + +def compare_sym_files(symfile0: SymbolFile, symfile1: SymbolFile): + if symfile0.name != symfile1.name: + print(f"{symfile0.name} does not match {symfile1.name}") + return False + + if len(symfile0.sections) != len(symfile1.sections): + return False + + if len(symfile0.symbols) != len(symfile1.symbols): + return False + + if is_out_of_order(symfile0.symbols, symfile1.symbols) == True: + return False + + return True + +def find_file_entry(files: List[SymbolFile], name: str) -> SymbolFile: + return [f for f in files if f.name == name][0] + +IGNORE_TU_SYM_ORDER = True + +def check_all_sym_files(files0: List[SymbolFile], files1: List[SymbolFile], completed: Dict[str, Object]): + + if len(files0) != len(files1): + print("Binaries have different file count") + return False + + diff_files: Tuple[SymbolFile, SymbolFile] = [] + + for a, b in zip(files0, files1): + + if a.name not in completed and b.name not in completed: + continue + + if a.name != b.name: + print(f"{a.name} does not match {b.name}") + + # if the tu names do not match and we shold not ignore tu order, skip + if not IGNORE_TU_SYM_ORDER: + continue + + # if tu names do not match + # find the matching entry in the built symbol table for the orig tu + b = find_file_entry(files1, a.name) + + diff_res: DiffFileResult = DiffFileResult(a, b) + if diff_res.has_diffs() or compare_sym_files(a, b) == False: + diff_files.append((a, b)) + + stray_mwcat_entries: List[str] = [] + for a, b in diff_files: + if len(a.sections) != len(b.sections): + orig_sects: Counter = count_sections_by_name(a.sections) + build_sects: Counter = count_sections_by_name(b.sections) + + if ".mwcats.text" in orig_sects: + if orig_sects[".mwcats.text"] > build_sects[".mwcats.text"]: + stray_mwcat_entries.append(a.name) + + print(f"{a}\nis different than\n{b}") + + print(f"{len(diff_files)} files different!") + print(f"{len(stray_mwcat_entries)} .mwcats.text diffs! After {stray_mwcat_entries}") + + return len(diff_files) == 0 + +def parse_nm(text: str) -> Dict[str, str]: + res = {} + + for line in text: + parts = line.split() + if len(parts) == 3: + res[parts[0]] = parts[2] + + return res + +def parse_sym_text(readelf_text: str, nm_text: str|None = None) -> List[SymbolFile]: + + text = readelf_text[1:] + + current_file: SymbolFile = None + + files: List[SymbolFile] = [] + + for line in text: + parsed_line: ParsedLine = ParsedLine(line, nm_text) + + if not parsed_line.valid: + continue + + if parsed_line.bind == "GLOBAL": + break + + if parsed_line.type == "FILE": + if not current_file: + # found file for the first time + current_file = SymbolFile(parsed_line) + files.append(current_file) + elif current_file: + current_file = SymbolFile(parsed_line) + files.append(current_file) + elif parsed_line.type == "SECTION": + current_file.addSection(parsed_line) + elif parsed_line.type == "OBJECT" or parsed_line.type == "FUNC" or parsed_line.type == "NOTYPE": + current_file.addSymbol(parsed_line) + + return files + +def parse_sym_file(filepath: str) -> List[SymbolFile]: + sym_lines = "" + + with open(filepath, "rt") as fd: + sym_lines = fd.readlines() + + return parse_sym_text(sym_lines) + +def read_elf_symbols(elf_path) -> Tuple[str, str]: + import subprocess + + if USE_BINUTILS: + readelf_cmd = [READELF, "--symbols", elf_path] + else: + readelf_cmd = ["python", CUSTOM_READELF_PY_PATH, "--symbols", elf_path] + + readelf_result = subprocess.run(readelf_cmd, capture_output=True, text=True, shell=True) + + nm_result = None + if USE_BINUTILS: + nm_cmd = [NM, "-p", elf_path] + nm_result = subprocess.run(nm_cmd, capture_output=True, text=True, shell=True) + + if readelf_result.returncode == 0: + if nm_result and nm_result.returncode == 0: + return (readelf_result.stdout.splitlines()[2:], nm_result.stdout.splitlines()[2:]) + else: + return (readelf_result.stdout.splitlines()[2:], "") + else: + return ("", "") + +def check_symbols(config: ProjectConfig): + import os + + orig_files = parse_sym_file(ORIG_ELF_SYMBOLS) + elf_file_path = os.path.join(os.getcwd(), config.out_path(), "main.elf").replace("\\", "/") + + readelf_out, nm_out = read_elf_symbols(elf_file_path) + + parsed_nm_info = None + if nm_out != "": + parsed_nm_info = parse_nm(nm_out) + + built_files = parse_sym_text(readelf_out, parsed_nm_info) + + objects: Dict[str, Object] = config.objects() + + completed_objs: Dict[str, Object] = {} + + for obj_name in objects: + if objects[obj_name].completed: + completed_objs[obj_name.split('/')[-1]] = objects[obj_name] + + are_same = check_all_sym_files(orig_files, built_files, completed_objs) + if are_same: + print("Symbol Tables Match!") + else: + print("Symbol Tables Differ!")