summaryrefslogtreecommitdiff
path: root/extract_assets.py
blob: 4e027655a33421cc193ca85ef1141e6368e0e197 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/usr/bin/env python3

import argparse, json, os, signal, time, sys, shutil
from multiprocessing import Pool, cpu_count, Event, Manager, ProcessError

#EXTRACTED_ASSETS_NAMEFILE = ".extracted-assets.json"


def SignalHandler(sig, frame):
    print(f'Signal {sig} received. Aborting...')
    mainAbort.set()
    # Don't exit immediately to update the extracted assets file.

def BuildOTR():
    #if globalAbort.is_set():
        # Don't extract if another file wasn't extracted properly.
    #    return
    
    shutil.copyfile("baserom/Audiobank", "Extract/Audiobank")
    shutil.copyfile("baserom/Audioseq", "Extract/Audioseq")
    shutil.copyfile("baserom/Audiotable", "Extract/Audiotable")

    execStr = "x64\\Release\\ZAPD.exe" if sys.platform == "win32" else "../ZAPD/ZAPD.out"

    execStr += " botr -se OTR"

    print(execStr)
    exitValue = os.system(execStr)
    if exitValue != 0:
    #    globalAbort.set()
        print("\n")
        print("Error when building the OTR file...", file=os.sys.stderr)
        print("Aborting...", file=os.sys.stderr)
        print("\n")

def ExtractFile(xmlPath, outputPath, outputSourcePath):
    #if globalAbort.is_set():
        # Don't extract if another file wasn't extracted properly.
    #    return

    execStr = "x64\\Release\\ZAPD.exe" if sys.platform == "win32" else "../ZAPD/ZAPD.out"

    #execStr += " e -eh -i %s -b baserom/ -o %s -osf %s -gsf 1 -rconf CFG/Config.xml -se OTR" % (xmlPath, outputPath, outputSourcePath)
    execStr += " e -eh -i %s -b baserom/ -o %s -osf %s -gsf 1 -rconf CFG/Config.xml -se OTR" % (xmlPath, outputPath, outputSourcePath)

    if "overlays" in xmlPath:
        execStr += " --static"

    print(execStr)
    exitValue = os.system(execStr)
    #exitValue = 0
    if exitValue != 0:
    #    globalAbort.set()
        print("\n")
        print("Error when extracting from file " + xmlPath, file=os.sys.stderr)
        print("Aborting...", file=os.sys.stderr)
        print("\n")

def ExtractFunc(fullPath):
    *pathList, xmlName = fullPath.split(os.sep)
    objectName = os.path.splitext(xmlName)[0]

    outPath = os.path.join("..\\soh\\assets\\", *pathList[4:], objectName)
    os.makedirs(outPath, exist_ok=True)
    outSourcePath = outPath

    #if fullPath in globalExtractedAssetsTracker:
    #    timestamp = globalExtractedAssetsTracker[fullPath]["timestamp"]
    #    modificationTime = int(os.path.getmtime(fullPath))
    #    if modificationTime < timestamp:
    #        # XML has not been modified since last extraction.
    #        return

    #currentTimeStamp = int(time.time())

    ExtractFile(fullPath, outPath, outSourcePath)

    #if not globalAbort.is_set():
    #    # Only update timestamp on succesful extractions
    #    if fullPath not in globalExtractedAssetsTracker:
    #        globalExtractedAssetsTracker[fullPath] = globalManager.dict()
    #    globalExtractedAssetsTracker[fullPath]["timestamp"] = currentTimeStamp

#def initializeWorker(abort, unaccounted: bool, extractedAssetsTracker: dict, manager):
def initializeWorker(abort, test):
    global globalAbort
    #global globalUnaccounted
    #global globalExtractedAssetsTracker
    #global globalManager
    globalAbort = abort
    #globalUnaccounted = unaccounted
    #globalExtractedAssetsTracker = extractedAssetsTracker
    #globalManager = manager


def main():
    parser = argparse.ArgumentParser(description="baserom asset extractor")
    parser.add_argument("-s", "--single", help="asset path relative to assets/, e.g. objects/gameplay_keep")
    parser.add_argument("-f", "--force", help="Force the extraction of every xml instead of checking the touched ones.", action="store_true")
    parser.add_argument("-u", "--unaccounted", help="Enables ZAPD unaccounted detector warning system.", action="store_true")
    args = parser.parse_args()

    global mainAbort
    mainAbort = Event()
    manager = Manager()
    signal.signal(signal.SIGINT, SignalHandler)

    extractedAssetsTracker = manager.dict()
    #if os.path.exists(EXTRACTED_ASSETS_NAMEFILE) and not args.force:
    #    with open(EXTRACTED_ASSETS_NAMEFILE, encoding='utf-8') as f:
    #        extractedAssetsTracker.update(json.load(f, object_hook=manager.dict))

    asset_path = args.single
    if asset_path is not None:
        fullPath = os.path.join("..\\soh\\assets", "xml", asset_path + ".xml")
        if not os.path.exists(fullPath):
            print(f"Error. File {fullPath} doesn't exists.", file=os.sys.stderr)
            exit(1)

        # Always extract if -s is used.
    #    if fullPath in extractedAssetsTracker:
    #        del extractedAssetsTracker[fullPath]
        ExtractFunc(fullPath)
    else:
        extract_text_path = "assets/text/message_data.h"
        if os.path.isfile(extract_text_path):
            extract_text_path = None
        extract_staff_text_path = "assets/text/message_data_staff.h"
        if os.path.isfile(extract_staff_text_path):
            extract_staff_text_path = None

        xmlFiles = []
        for currentPath, _, files in os.walk(os.path.join("..\\soh\\assets", "xml")):
            for file in files:
                fullPath = os.path.join(currentPath, file)
                if file.endswith(".xml"):
                    xmlFiles.append(fullPath)

        try:
            numCores = 2
            print("Extracting assets with " + str(numCores) + " CPU cores.")
            #with Pool(numCores,  initializer=initializeWorker, initargs=(mainAbort, args.unaccounted, extractedAssetsTracker, manager)) as p:
            with Pool(numCores, initializer=initializeWorker, initargs=(mainAbort, 0)) as p:
                p.map(ExtractFunc, xmlFiles)
        except Exception as e:
            print("Warning: Multiprocessing exception ocurred.", file=os.sys.stderr)
            print("Disabling mutliprocessing.", file=os.sys.stderr)

            initializeWorker(mainAbort, 0)
            for singlePath in xmlFiles:
                ExtractFunc(singlePath)


        BuildOTR()
        os.rmdir("Extract") # OTRTODO: This line does not work...

    #with open(EXTRACTED_ASSETS_NAMEFILE, 'w', encoding='utf-8') as f:
    #    serializableDict = dict()
    #    for xml, data in extractedAssetsTracker.items():
    #        serializableDict[xml] = dict(data)
    #    json.dump(dict(serializableDict), f, ensure_ascii=False, indent=4)

    #if mainAbort.is_set():
    #    exit(1)

if __name__ == "__main__":
    main()