From 2232407d41f16d4a7c0a7304a86e239c39e6731b Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Thu, 14 Sep 2023 19:49:26 -0700 Subject: [PATCH 1/7] Fix RebuildingControlFlow() Champollion was missing a LOT of conditionals. --- Decompiler/PscDecompiler.cpp | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/Decompiler/PscDecompiler.cpp b/Decompiler/PscDecompiler.cpp index 059689a..3642fe3 100644 --- a/Decompiler/PscDecompiler.cpp +++ b/Decompiler/PscDecompiler.cpp @@ -479,7 +479,9 @@ void Decompiler::PscDecompiler::createNodesForBlocks(size_t block) node = std::make_shared(ip, args[0].getId(), fromValue(ip, args[1]), typeOfVar(args[0].getId())); } else // two variables of the same type, equivalent to an assign { - node = std::make_shared(ip, args[0].getId(), fromValue(ip, args[1])); + // check if this is a useless cast + if (args[0].getId() != args[1].getId()) + node = std::make_shared(ip, args[0].getId(), fromValue(ip, args[1])); } break; } @@ -1038,7 +1040,17 @@ Node::BasePtr Decompiler::PscDecompiler::rebuildControlFlow(size_t startBlock, s // Decompilation failed throw std::runtime_error("Decompilation failed"); } - auto& lastBlock = m_CodeBlocs[beforeExit]; + Node::BasePtr condition = std::make_shared(-1, Pex::Value(source->getCondition(), true)); + + if (m_CodeBlocs[beforeExit] == source){ + assert(source->onFalse() < source->onTrue()); + auto scope = source->getScope(); + condition = std::make_shared(-1, 10, source->getCondition(), "!", condition); + source->setCondition(source->getCondition(), source->onFalse(), source->onTrue()); + exit = source->onFalse(); + beforeExit = findBlockForInstruction(exit-1); + } + auto& lastBlock = m_CodeBlocs[beforeExit]; // The last block is an unconditional jump to the current block // This is a while. @@ -1048,15 +1060,12 @@ Node::BasePtr Decompiler::PscDecompiler::rebuildControlFlow(size_t startBlock, s auto whileStartBlock = source->onTrue(); auto whileEndBlock = source->onFalse(); - - Node::BasePtr whileCondition = std::make_shared(-1, Pex::Value(source->getCondition(), true)); - result->mergeChildren(source->getScope()->shared_from_this()); // Rebuild the statements in the while loop. auto whileBody = rebuildControlFlow(whileStartBlock, whileEndBlock); - *result << std::make_shared(-1, whileCondition, whileBody); + *result << std::make_shared(-1, condition, whileBody); advance = 0; it = m_CodeBlocs.find(whileEndBlock); @@ -1071,14 +1080,12 @@ Node::BasePtr Decompiler::PscDecompiler::rebuildControlFlow(size_t startBlock, s auto ifStartBlock = source->onTrue(); auto ifEndBlock = source->onFalse(); - Node::BasePtr ifCondition = std::make_shared(-1, Pex::Value(source->getCondition(), true)); - result->mergeChildren(source->getScope()->shared_from_this()); // Rebuild the statements of the if body auto ifBody = rebuildControlFlow(ifStartBlock, ifEndBlock); - *result << std::make_shared(-1, ifCondition, ifBody, nullptr); + *result << std::make_shared(-1, condition, ifBody, nullptr); advance = 0; it = m_CodeBlocs.find(ifEndBlock); @@ -1089,7 +1096,6 @@ Node::BasePtr Decompiler::PscDecompiler::rebuildControlFlow(size_t startBlock, s auto elseStartBlock = source->onFalse(); auto endElseBlock = lastBlock->getNext(); - Node::BasePtr ifCondition = std::make_shared(-1, Pex::Value(source->getCondition(), true)); result->mergeChildren(source->getScope()->shared_from_this()); @@ -1098,7 +1104,7 @@ Node::BasePtr Decompiler::PscDecompiler::rebuildControlFlow(size_t startBlock, s // Rebuilds the statements in the else body. auto elseBody = rebuildControlFlow(elseStartBlock, endElseBlock); - *result << std::make_shared(-1, ifCondition, ifBody, elseBody); + *result << std::make_shared(-1, condition, ifBody, elseBody); advance = 0; it = m_CodeBlocs.find(endElseBlock); From a9b1f4e344b293069346bb63d94c4cda8a11abbf Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Thu, 14 Sep 2023 21:01:22 -0700 Subject: [PATCH 2/7] Print warning when decompiling Starfield scripts --- Champollion/main.cpp | 53 +++++++++++++++++++++++++++------ Decompiler/PscCodeGenerator.cpp | 2 +- Decompiler/PscCoder.cpp | 2 +- Decompiler/PscCoder.hpp | 1 + 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/Champollion/main.cpp b/Champollion/main.cpp index 65b5d97..b32bb41 100644 --- a/Champollion/main.cpp +++ b/Champollion/main.cpp @@ -179,7 +179,12 @@ OptionsResult getProgramOptions(int argc, char* argv[], Params& params) return Good; } -typedef std::vector ProcessResults; +struct _ProcessResults{ + std::vector output; + bool isStarfield = false; +}; + +typedef _ProcessResults ProcessResults; ProcessResults processFile(fs::path file, Params params) { ProcessResults result; @@ -192,9 +197,10 @@ ProcessResults processFile(fs::path file, Params params) } catch(std::exception& ex) { - result.push_back(std::format("ERROR: {} : {}", file.string(), ex.what())); + result.output.push_back(std::format("ERROR: {} : {}", file.string(), ex.what())); return result; } + pex.getGameType() == Pex::Binary::StarfieldScript ? result.isStarfield = true : result.isStarfield = false; if (params.outputAssembly) { fs::path asmFile = params.assemblyDir / file.filename().replace_extension(".pas"); @@ -204,11 +210,11 @@ ProcessResults processFile(fs::path file, Params params) Decompiler::AsmCoder asmCoder(new Decompiler::StreamWriter(asmStream)); asmCoder.code(pex); - result.push_back(std::format("{} dissassembled to {}", file.string(), asmFile.string())); + result.output.push_back(std::format("{} dissassembled to {}", file.string(), asmFile.string())); } catch(std::exception& ex) { - result.push_back(std::format("ERROR: {} : {}",file.string(),ex.what())); + result.output.push_back(std::format("ERROR: {} : {}", file.string(), ex.what())); fs::remove(asmFile); } } @@ -240,11 +246,11 @@ ProcessResults processFile(fs::path file, Params params) params.papyrusDir.string()); // using string instead of path here for C++14 compatability for staticlib targets pscCoder.code(pex); - result.push_back(std::format("{} decompiled to {}", file.string(), pscFile.string())); + result.output.push_back(std::format("{} decompiled to {}", file.string(), pscFile.string())); } catch(std::exception& ex) { - result.push_back(std::format("ERROR: {} : {}", file.string() , ex.what())); + result.output.push_back(std::format("ERROR: {} : {}", file.string() , ex.what())); fs::remove(pscFile); } return result; @@ -257,6 +263,7 @@ int main(int argc, char* argv[]) Params args; size_t countFiles = 0; auto result = getProgramOptions(argc, argv, args); + bool printStarfieldWarning = false; if (result == Good) { auto start = std::chrono::steady_clock::now(); @@ -272,7 +279,11 @@ int main(int argc, char* argv[]) { if (_stricmp(entry->path().extension().string().c_str(), ".pex") == 0) { - for (auto line : processFile(entry->path(), args)) + auto processResult = processFile(path, args); + if (!printStarfieldWarning && processResult.isStarfield){ + printStarfieldWarning = true; + } + for (auto line : processResult.output) { std::cout << line << '\n'; } @@ -284,7 +295,11 @@ int main(int argc, char* argv[]) else { ++countFiles; - for (auto line : processFile(path, args)) + auto processResult = processFile(path, args); + if (!printStarfieldWarning && processResult.isStarfield){ + printStarfieldWarning = true; + } + for (auto line : processResult.output) { std::cout << line << '\n'; } @@ -318,7 +333,12 @@ int main(int argc, char* argv[]) for (auto& result : results) { - for(auto& line : result.get()) + auto processResult = result.get(); + if (!printStarfieldWarning && processResult.isStarfield){ + printStarfieldWarning = true; + } + + for(auto& line : processResult.output) { std::cout << line << '\n'; } @@ -330,6 +350,21 @@ int main(int argc, char* argv[]) auto diff = end - start; std::cout << countFiles << " files processed in " << std::chrono::duration (diff).count() << " s" << std::endl; + + + if (printStarfieldWarning){ + // TODO: Remove this warning when the CK comes out + std::cout << "********************* STARFIELD PRELIMINARY SYNTAX WARNING *********************" << std::endl; + std::cout << "The syntax for new features in Starfield (Guard, TryGuard, GetMatchingStructs) is not yet known." << std::endl; + std::cout << "Decompiled Starfield scripts use guessed-at syntax for these features." << std::endl; + std::cout << "This syntax should be considered as experimental, unstable, and subject to change." << std::endl << std::endl; + std::cout << "The proper syntax will only be known when the Creation Kit comes out in early 2024." << std::endl; + std::cout << "If you are using decompiled scripts as the basis for mods, please be aware of this," << std::endl; + std::cout << "and be prepared to update your scripts when the final syntax is known." << std::endl << std::endl; + std::cout << "The lines in the decompiled scripts which contain this guessed-at syntax are" << std::endl; + std::cout << "marked with a comment beginning with '" << Decompiler::WARNING_COMMENT_PREFIX << "'." << std::endl; + std::cout << "********************* STARFIELD PRELIMINARY SYNTAX WARNING *********************" << std::endl; + } return 0; } if (result == HelpOrVersion){ diff --git a/Decompiler/PscCodeGenerator.cpp b/Decompiler/PscCodeGenerator.cpp index 15819be..15278b4 100644 --- a/Decompiler/PscCodeGenerator.cpp +++ b/Decompiler/PscCodeGenerator.cpp @@ -32,7 +32,7 @@ Decompiler::PscCodeGenerator::PscCodeGenerator(Decompiler::PscDecompiler* decomp void Decompiler::PscCodeGenerator::newLine() { if (!m_ExperimentalSyntaxWarning.empty()) { - m_Result << " ;*** WARNING: Experimental syntax, may be incorrect: "; + m_Result << " " << Decompiler::WARNING_COMMENT_PREFIX << " WARNING: Experimental syntax, may be incorrect: "; for (auto warn: m_ExperimentalSyntaxWarning){ m_Result << warn << " "; } diff --git a/Decompiler/PscCoder.cpp b/Decompiler/PscCoder.cpp index df0af64..a8e14aa 100644 --- a/Decompiler/PscCoder.cpp +++ b/Decompiler/PscCoder.cpp @@ -216,7 +216,7 @@ void Decompiler::PscCoder::writeObject(const Pex::Object &object, const Pex::Bin if (object.getGuards().size()) { write(""); write(";-- Guards ------------------------------------------"); - write(";*** WARNING: Guard declaration syntax is EXPERIMENTAL, subject to change"); + write(std::string(Decompiler::WARNING_COMMENT_PREFIX) + " WARNING: Guard declaration syntax is EXPERIMENTAL, subject to change"); writeGuards(object, pex); } diff --git a/Decompiler/PscCoder.hpp b/Decompiler/PscCoder.hpp index a142404..325dcb3 100644 --- a/Decompiler/PscCoder.hpp +++ b/Decompiler/PscCoder.hpp @@ -3,6 +3,7 @@ namespace Decompiler { +static const char* WARNING_COMMENT_PREFIX = ";***"; /** * @brief Write a PEX file as a PSC file. */ From 185e368838b104a62b3d14f81d31920397e511a7 Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Thu, 14 Sep 2023 21:29:05 -0700 Subject: [PATCH 3/7] Check for orphaned nodes when compiling and throw error if true --- Decompiler/PscDecompiler.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Decompiler/PscDecompiler.cpp b/Decompiler/PscDecompiler.cpp index 3642fe3..55e972f 100644 --- a/Decompiler/PscDecompiler.cpp +++ b/Decompiler/PscDecompiler.cpp @@ -1382,6 +1382,17 @@ void Decompiler::PscDecompiler::cleanUpTree(Node::BasePtr program) program->computeInstructionBounds(); + // check for orphaned nodes on m_CodeBlocs + for (auto& bloc_kv : m_CodeBlocs) + { + auto& bloc = bloc_kv.second; + auto scope = bloc->getScope(); + if (scope->size() > 0) { + auto funcname = m_Function.getName().isValid() ? m_Function.getName().asString() : "unknown function"; + throw std::runtime_error("Orphaned nodes in " + funcname + " from instruction " + std::to_string(scope->front()->getBegin()) + " to " + std::to_string(scope->back()->getEnd()) + "."); + } + } + } From 3f7ea17e5d0755472d2a887ac0a50a661591c9eb Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Thu, 14 Sep 2023 21:29:18 -0700 Subject: [PATCH 4/7] Improved decompiling error messages --- Decompiler/PscDecompiler.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Decompiler/PscDecompiler.cpp b/Decompiler/PscDecompiler.cpp index 55e972f..0dde76f 100644 --- a/Decompiler/PscDecompiler.cpp +++ b/Decompiler/PscDecompiler.cpp @@ -828,7 +828,8 @@ void Decompiler::PscDecompiler::rebuildExpression(Node::BasePtr scope) } else { - throw std::runtime_error("Decompilation failed"); + auto funcname = m_Function.getName().isValid() ? m_Function.getName().asString() : "unknown function"; + throw std::runtime_error("Failed to rebuild expression in " + funcname + " at instruction " + std::to_string(expressionUse->getBegin())); } } else @@ -1017,7 +1018,8 @@ Node::BasePtr Decompiler::PscDecompiler::rebuildControlFlow(size_t startBlock, s { if (endBlock < startBlock) { - throw std::runtime_error("Decompilation failed"); + auto funcname = m_Function.getName().isValid() ? m_Function.getName().asString() : "unknown function"; + throw std::runtime_error("Failed to rebuild control flow for " + funcname + "."); } auto begin = m_CodeBlocs.find(startBlock); auto end = m_CodeBlocs.find(endBlock); @@ -1038,7 +1040,8 @@ Node::BasePtr Decompiler::PscDecompiler::rebuildControlFlow(size_t startBlock, s if (beforeExit == PscCodeBlock::END) { // Decompilation failed - throw std::runtime_error("Decompilation failed"); + auto funcname = m_Function.getName().isValid() ? m_Function.getName().asString() : "unknown function"; + throw std::runtime_error("Failed to rebuild control flow for " + funcname + "."); } Node::BasePtr condition = std::make_shared(-1, Pex::Value(source->getCondition(), true)); From 6e1dc7480b0838f3c4ed71757bc9496411c98180 Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Thu, 14 Sep 2023 21:43:30 -0700 Subject: [PATCH 5/7] Output number of failed files at end --- Champollion/main.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Champollion/main.cpp b/Champollion/main.cpp index b32bb41..1eaecd7 100644 --- a/Champollion/main.cpp +++ b/Champollion/main.cpp @@ -182,6 +182,7 @@ OptionsResult getProgramOptions(int argc, char* argv[], Params& params) struct _ProcessResults{ std::vector output; bool isStarfield = false; + bool failed = false; }; typedef _ProcessResults ProcessResults; @@ -198,6 +199,7 @@ ProcessResults processFile(fs::path file, Params params) catch(std::exception& ex) { result.output.push_back(std::format("ERROR: {} : {}", file.string(), ex.what())); + result.failed = true; return result; } pex.getGameType() == Pex::Binary::StarfieldScript ? result.isStarfield = true : result.isStarfield = false; @@ -215,6 +217,7 @@ ProcessResults processFile(fs::path file, Params params) catch(std::exception& ex) { result.output.push_back(std::format("ERROR: {} : {}", file.string(), ex.what())); + result.failed = true; fs::remove(asmFile); } } @@ -251,18 +254,21 @@ ProcessResults processFile(fs::path file, Params params) catch(std::exception& ex) { result.output.push_back(std::format("ERROR: {} : {}", file.string() , ex.what())); + result.failed = true; fs::remove(pscFile); } return result; } + int main(int argc, char* argv[]) { Params args; size_t countFiles = 0; auto result = getProgramOptions(argc, argv, args); + size_t failedFiles = 0; bool printStarfieldWarning = false; if (result == Good) { @@ -288,6 +294,9 @@ int main(int argc, char* argv[]) std::cout << line << '\n'; } ++countFiles; + if (processResult.failed){ + ++failedFiles; + } } entry++; } @@ -296,6 +305,10 @@ int main(int argc, char* argv[]) { ++countFiles; auto processResult = processFile(path, args); + if (processResult.failed){ + ++failedFiles; + } + if (!printStarfieldWarning && processResult.isStarfield){ printStarfieldWarning = true; } @@ -342,6 +355,9 @@ int main(int argc, char* argv[]) { std::cout << line << '\n'; } + if (processResult.failed){ + ++failedFiles; + } } countFiles = results.size(); @@ -350,7 +366,9 @@ int main(int argc, char* argv[]) auto diff = end - start; std::cout << countFiles << " files processed in " << std::chrono::duration (diff).count() << " s" << std::endl; - + if (failedFiles > 0){ + std::cout << failedFiles << " files failed to decompile." << std::endl; + } if (printStarfieldWarning){ // TODO: Remove this warning when the CK comes out From 0936ecbf55cc0eec8b1dd4548d49e8ee4ea5f1a8 Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Fri, 15 Sep 2023 10:01:52 -0700 Subject: [PATCH 6/7] add -r recursive option --- Champollion/main.cpp | 83 +++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/Champollion/main.cpp b/Champollion/main.cpp index 1eaecd7..a542ef4 100644 --- a/Champollion/main.cpp +++ b/Champollion/main.cpp @@ -30,10 +30,13 @@ struct Params bool dumpTree; bool recreateDirStructure; bool decompileDebugFuncs; + bool recursive; fs::path assemblyDir; fs::path papyrusDir; + fs::path parentDir{}; + std::vector inputs; }; @@ -62,6 +65,7 @@ OptionsResult getProgramOptions(int argc, char* argv[], Params& params) ("help,h", "Display the help message") ("asm,a", options::value()->implicit_value(""), "If defined, output assembly file(s) to this directory") ("psc,p", options::value(), "Name of the output dir for psc decompilation") + ("recursive,r", "Recursively scan directories for pex files") ("recreate-subdirs,s", "Recreates directory structure for script in root of output directory (Fallout 4 only, default false)") ("comment,c", "Output assembly in comments of the decompiled psc file") ("header,e", "Write header to decompiled psc file") @@ -110,6 +114,7 @@ OptionsResult getProgramOptions(int argc, char* argv[], Params& params) params.parallel = (args.count("threaded") != 0); params.traceDecompilation = (args.count("trace") != 0); params.dumpTree = params.traceDecompilation && args.count("no-dump-tree") == 0; + params.recursive = (args.count("recursive") != 0); params.recreateDirStructure = (args.count("recreate-subdirs") != 0); params.decompileDebugFuncs = (args.count("debug-funcs") != 0); try @@ -221,11 +226,13 @@ ProcessResults processFile(fs::path file, Params params) fs::remove(asmFile); } } - fs::path dir_structure = ""; + fs::path dir_structure; if (params.recreateDirStructure && (pex.getGameType() == Pex::Binary::Fallout4Script || pex.getGameType() == Pex::Binary::StarfieldScript) && pex.getObjects().size() > 0){ std::string script_path = pex.getObjects()[0].getName().asString(); std::replace(script_path.begin(), script_path.end(), ':', '/'); dir_structure = fs::path(script_path).remove_filename(); + } else if (!params.parentDir.empty()) { + dir_structure = fs::relative(file, params.parentDir).remove_filename(); } fs::path basedir = !dir_structure.empty() ? (params.papyrusDir / dir_structure) : params.papyrusDir; if (!dir_structure.empty()){ @@ -260,16 +267,30 @@ ProcessResults processFile(fs::path file, Params params) return result; } +size_t countFiles = 0; +size_t failedFiles = 0; +bool printStarfieldWarning = false; + +void processResult(ProcessResults result) +{ + if (result.failed){ + ++failedFiles; + } + if (!printStarfieldWarning && result.isStarfield){ + printStarfieldWarning = true; + } + for (auto line : result.output) + { + std::cout << line << '\n'; + } +} int main(int argc, char* argv[]) { Params args; - size_t countFiles = 0; auto result = getProgramOptions(argc, argv, args); - size_t failedFiles = 0; - bool printStarfieldWarning = false; if (result == Good) { auto start = std::chrono::steady_clock::now(); @@ -277,45 +298,31 @@ int main(int argc, char* argv[]) { for (auto path : args.inputs) { - if (fs::is_directory(path)) - { + if (args.recursive && fs::is_directory(path)){ + args.parentDir = path; + // recursively get all files in the directory + for (auto& entry : fs::recursive_directory_iterator(path)){ + if (fs::is_regular_file(entry) && _stricmp(entry.path().extension().string().c_str(), ".pex") == 0){ + processResult(processFile(entry, args)); + } + } + } else if (fs::is_directory(path)){ + args.parentDir = fs::path(); fs::directory_iterator end; fs::directory_iterator entry(path); while(entry != end) { if (_stricmp(entry->path().extension().string().c_str(), ".pex") == 0) { - auto processResult = processFile(path, args); - if (!printStarfieldWarning && processResult.isStarfield){ - printStarfieldWarning = true; - } - for (auto line : processResult.output) - { - std::cout << line << '\n'; - } - ++countFiles; - if (processResult.failed){ - ++failedFiles; - } + processResult(processFile(path, args)); } entry++; } } else { - ++countFiles; - auto processResult = processFile(path, args); - if (processResult.failed){ - ++failedFiles; - } - - if (!printStarfieldWarning && processResult.isStarfield){ - printStarfieldWarning = true; - } - for (auto line : processResult.output) - { - std::cout << line << '\n'; - } + args.parentDir = fs::path(); + processResult(processFile(path, args)); } } } @@ -324,8 +331,19 @@ int main(int argc, char* argv[]) std::vector> results; for (auto& path : args.inputs) { - if (fs::is_directory(path)) + + if (args.recursive && fs::is_directory(path)){ + args.parentDir = path; + // recursively get all files in the directory + for (auto& entry : fs::recursive_directory_iterator(path)){ + if (fs::is_regular_file(entry) && _stricmp(entry.path().extension().string().c_str(), ".pex") == 0){ + results.push_back(std::move(std::async(std::launch::async, processFile, fs::path(entry.path()), args))); + } + } + } + else if (fs::is_directory(path)) { + args.parentDir = fs::path(); fs::directory_iterator end; fs::directory_iterator entry(path); while(entry != end) @@ -340,6 +358,7 @@ int main(int argc, char* argv[]) } else { + args.parentDir = fs::path(); results.push_back(std::move(std::async(std::launch::async, processFile, path, args))); } } From caaed11d0457cc15146341bdfa5b821bb47da150 Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Fri, 15 Sep 2023 10:04:55 -0700 Subject: [PATCH 7/7] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 383aae2..2ebb225 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Champollion is a CLI-only program. | -a [*assembly directory*] | --asm [*assembly directory*] | Champollion will write an assembly version of the PEX file in the given directory, if one. The assembly file is an human readable version of the content of the PEX file | | -c | --comment | The decompiled file will be annotated with the assembly instruction corresponding to the decompiled code lines. | | -t | --threaded | Champollion will parallelize the decompilation. It is useful when decompiling a directory containing many PEX files. | +| -r | --recursive | Recursively scan specified directory(s) for pex files to decompile| | -s | --recreate-subdirs | Recreates directory structure for script in root of output directory (Fallout 4 only, default false) | | -e | --header | Write header to decompiled psc file | | -g | --trace | Trace the decompilation and output results to rebuild log |