#!/usr/bin/env python3
"""Extract the I2 Localization term table (English) from SAND's I2Languages asset.

The typetree read trips on an I2 alignment quirk, but each TermData is
self-describing, so we parse the MonoBehaviour body directly. Layout (Unity
serialization, little-endian, 4-byte aligned strings/arrays), from dump.cs:

  engine header: m_GameObject(12) m_Enabled(1,align4) m_Script(12) m_Name(string)
  LanguageSourceData mSource:
    UInt8 x3 flags (align4)
    TermData[] mTerms:           int count, then per term:
       string Term
       int32  TermType
       string Description
       string[] Languages        (the translations, one per language)
       byte[]  Flags
       string[] Languages_Touch
    UInt8 CaseInsensitiveTerms (align4)
    int32 OnMissingTranslation
    string mTerm_AppName
    LanguageData[] mLanguages:   int count, then per language:
       string Name; string Code; byte Flags; byte Compressed   (align4)
"""
import os, struct, json

GAME = "/mnt/d/SteamLibrary/steamapps/common/Sand Playtest"
DU = os.path.join(GAME, "Sand_Data/data.unity3d")
OUT = "/home/downloadpizza/sand_tools/i2_terms_en.json"

class R:
    def __init__(self, b, off=0): self.b=b; self.o=off
    def align(self):
        self.o = (self.o + 3) & ~3
    def u8(self):
        v=self.b[self.o]; self.o+=1; return v
    def i32(self):
        v=struct.unpack_from('<i',self.b,self.o)[0]; self.o+=4; return v
    def i64(self):
        v=struct.unpack_from('<q',self.b,self.o)[0]; self.o+=8; return v
    def s(self):
        n=self.i32()
        if n<0 or n>10_000_000: raise ValueError(f"bad str len {n} @ {self.o}")
        v=self.b[self.o:self.o+n].decode('utf-8','replace'); self.o+=n; self.align(); return v
    def bytes(self):
        n=self.i32(); v=self.b[self.o:self.o+n]; self.o+=n; self.align(); return v
    def str_array(self):
        n=self.i32(); return [self.s() for _ in range(n)]

def parse(raw):
    r=R(raw)
    # engine header
    r.i32(); r.i64()              # m_GameObject
    r.u8(); r.align()            # m_Enabled
    r.i32(); r.i64()              # m_Script
    name=r.s()                    # m_Name
    # mSource — each bool is individually 4-byte aligned
    r.u8(); r.align(); r.u8(); r.align(); r.u8(); r.align()
    nterms=r.i32()
    terms=[]
    for _ in range(nterms):
        term=r.s()
        desc=r.s()              # TermType is not serialized; Description (usually empty)
        langs=r.str_array()     # the translations, one per language
        flags=r.bytes()         # one flag byte per language
        touch=r.str_array()     # Languages_Touch
        terms.append((term, langs))
    languages=[]
    try:                          # trailing block is best-effort (English is index 0 regardless)
        r.u8(); r.align()         # CaseInsensitiveTerms
        r.i32()                   # OnMissingTranslation
        r.s()                     # mTerm_AppName
        nlang=r.i32()
        for _ in range(nlang):
            lname=r.s(); lcode=r.s(); r.u8(); r.align(); r.u8(); r.align()
            languages.append((lname,lcode))
    except Exception:
        pass
    return name, languages, terms

def main():
    import UnityPy
    env=UnityPy.load(DU)
    obj=next(o for o in env.objects if o.type.name=='MonoBehaviour'
             and len(o.get_raw_data())==3816792)
    name, languages, terms = parse(obj.get_raw_data())
    print("asset:",name,"| languages:",languages)
    eng=next((i for i,(n,c) in enumerate(languages)
              if c.lower().startswith('en') or n.lower().startswith('english')),0)
    print("english index:",eng,"| terms:",len(terms))
    table={t:(tr[eng] if eng<len(tr) else (tr[0] if tr else '')) for t,tr in terms}
    json.dump({'_source':'I2 LanguageSourceAsset (I2Languages), data.unity3d',
               'languages':languages,'english_index':eng,'count':len(table),
               'terms':table}, open(OUT,'w'), indent=2, ensure_ascii=False)
    from collections import Counter
    ns=Counter(k.split('/')[0] for k in table)
    print("\ntop term-key prefixes:")
    for k,c in ns.most_common(30): print(f"  {c:4d}  {k}")
    print("\nwrote",OUT)

if __name__=='__main__':
    main()
