diff --git a/frontend/public/icons/wizard.svg b/frontend/public/icons/wizard.svg new file mode 100644 index 000000000..f3f27b97c --- /dev/null +++ b/frontend/public/icons/wizard.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index acf3c4ec8..502887671 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -42,20 +42,23 @@ selectedResource, settings, dataLength, + viewCrumbs, } from "./stores.js"; import { keyEventToString, shortcuts } from "./keyboard.js"; + import RootWizardView from "./patch_wizard/RootWizardView.svelte"; + printConsoleArt(); - let showRootResource = false, - showProjectManager = false, - dataLenPromise = Promise.resolve([]), + let dataLenPromise = Promise.resolve([]), useAssemblyView = false, useTextView = false, rootResourceLoadPromise = new Promise((resolve) => {}), resources = {}; let currentResource, rootResource, modifierView, bottomLeftPane; + let topLevelView = null; + // TODO: Move to settings let riddleAnswered = JSON.parse(window.localStorage.getItem("riddleSolved")); if (riddleAnswered === null || riddleAnswered === undefined) { @@ -154,12 +157,14 @@ Answer by running riddle.answer('your answer here') from the console.`); $: docstyle.setProperty("--accent-text-color", $settings.accentText); $: docstyle.setProperty("--last-modified-color", $settings.lastModified); $: docstyle.setProperty("--all-modified-color", $settings.allModified); + + $: topLevelView = $viewCrumbs[$viewCrumbs.length - 1]; -{#if showRootResource} +{#if topLevelView === "rootResource"} {#await rootResourceLoadPromise} {:then _} @@ -176,8 +181,6 @@ Answer by running riddle.answer('your answer here') from the console.`); rootResource="{rootResource}" bind:bottomLeftPane="{bottomLeftPane}" bind:modifierView="{modifierView}" - bind:showProjectManager="{showProjectManager}" - bind:showRootResource="{showRootResource}" /> {/if} @@ -207,19 +210,17 @@ Answer by running riddle.answer('your answer here') from the console.`); {/if} -{:else if showProjectManager} +{:else if topLevelView === "projectManager"} -{:else} +{:else if topLevelView === "patchWizard"} + +{:else if topLevelView === "start"} diff --git a/frontend/src/ofrak/remote_resource.js b/frontend/src/ofrak/remote_resource.js index 77cad8187..03dd07202 100644 --- a/frontend/src/ofrak/remote_resource.js +++ b/frontend/src/ofrak/remote_resource.js @@ -646,7 +646,7 @@ export class RemoteResource extends Resource { }); } - async run_component(component, configtype, response) { + async run_component(component, configtype, config) { const result = await fetch( `${this.uri}/run_component?component=${component}`, { @@ -654,7 +654,7 @@ export class RemoteResource extends Resource { headers: { "Content-Type": "application/json", }, - body: JSON.stringify([configtype, response]), + body: JSON.stringify(config), } ).then(async (r) => { if (!r.ok) { diff --git a/frontend/src/patch_wizard/FreeSpaceView.svelte b/frontend/src/patch_wizard/FreeSpaceView.svelte new file mode 100644 index 000000000..032bc910b --- /dev/null +++ b/frontend/src/patch_wizard/FreeSpaceView.svelte @@ -0,0 +1,408 @@ + + + + +{#if unallocatedSegments.length > 0} +

+ {unallocatedSegments.length} segment(s) totaling 0x{totalSize( + unallocatedSegments + ).toString(16)} bytes still need to be allocated. +

+{:else if patchInfo?.objectInfos} +

+ No unallocated segments (out of {patchInfo.objectInfos + .map((objInfo) => objInfo.segments) + .flat() + .filter((seg) => seg.include).length} segments included). +

+{:else} +

No segments.

+{/if} +
+
+
+

Segments to allocate:

+ R + W + X + + Unallocated Only +
+ +

+ Total {unallocatedOnly ? "unallocated" : ""} + {permsToString(segFilterR, segFilterW, segFilterX)} segment size: 0x{totalSize( + filteredUnallocatedSegments + ).toString(16)} +

+ + {#each filteredUnallocatedSegments as segInfo} + + {/each} +
+ +
+
+

Free Space to allocate:

+ R + W + X +
+ + {#await freeSpacePromise} +

Fetching free space...

+ {:then freeSpace} + {#await filteredFreeSpacePromise} +

Processing free space...

+ {:then filteredFreeSpace} +

+ {filteredFreeSpace.length} matching blocks of {permsToString( + freeFilterR, + freeFilterW, + freeFilterX + )} free space (out of {freeSpace.map(([_, ranges]) => ranges).flat() + .length} total ranges with {freeSpace.length} unique permission spec{freeSpace.length > + 1 + ? "s" + : ""}). +

+ {#each filteredFreeSpace as [blockStart, blockEnd]} +

+ [0x{blockStart.toString(16)}-0x{blockEnd.toString(16)} (0x{( + blockEnd - blockStart + ).toString(16)} bytes)] +

+ {/each} +

+ Total {permsToString(freeFilterR, freeFilterW, freeFilterX)} free space: + 0x{filteredFreeSpace + .reduce((sum, [start, end]) => sum + end - start, 0) + .toString(16)} +

+ {/await} + {/await} +
+
+ +
+

Create Free Space: Extend Binary (ELF Only)

+
+

Recover Unused Alignment

+

+ Reclaim unused alignment bytes between adjacent PT_LOAD segment in an ELF + and tag them as free space. Alignment bytes often exist between PT_LOAD + sections in ELF binaries. These alignment bytes are added to the preceding + PT_LOAD segment. This is the least invasive ELF extension technique. +

+ + + {#if patchInfo.extensionMethodStatus.load_align.status} + {#if patchInfo.extensionMethodStatus.load_align.valid} +

Success! {patchInfo.extensionMethodStatus.load_align.status}

+ {:else} +

+ {patchInfo.extensionMethodStatus.load_align.status} +

+ {/if} + {/if} +
+
+

Replace NOTE Segment

+

+ If the ELF has a .NOTE segment, we can replace the .NOTE header with a + .LOAD header pointing to some new space we weill add at the end. Adds + 0x1000 bytes by default. WARNING: Will require the ELF to be re-analyzed + and unpacked. +

+ + {#if patchInfo.extensionMethodStatus.note.status} + {#if patchInfo.extensionMethodStatus.note.valid} +

Success! {patchInfo.extensionMethodStatus.note.status}

+ {:else} +

{patchInfo.extensionMethodStatus.note.status}

+ {/if} + {/if} +
+
+ +
+

Create Free Space: Overwrite Existing Functions

+ + {#await complexBlocksPromise} + + {:then complexBlocks} +
+ {#each complexBlocks as cbObject} +
+ +

+ {cbObject.name} (0x{cbObject.vaddr.toString(16)}-0x{( + cbObject.vaddr + cbObject.size + ).toString(16)}) +

+
+ {/each} +
+ +

+ 0x{totalSelectedComplexBlockSize( + complexBlocksToFree, + complexBlocks + ).toString(16)} bytes of RX space will be created. +

+ + + {/await} +
diff --git a/frontend/src/patch_wizard/ObjectMappingView.svelte b/frontend/src/patch_wizard/ObjectMappingView.svelte new file mode 100644 index 000000000..ea151f706 --- /dev/null +++ b/frontend/src/patch_wizard/ObjectMappingView.svelte @@ -0,0 +1,15 @@ + + +
+ {#each patchInfo.objectInfos as objInfo} + + {/each} +
diff --git a/frontend/src/patch_wizard/ObjectWidget.svelte b/frontend/src/patch_wizard/ObjectWidget.svelte new file mode 100644 index 000000000..a86234f02 --- /dev/null +++ b/frontend/src/patch_wizard/ObjectWidget.svelte @@ -0,0 +1,102 @@ + + + + +
+
+ {objectInfo.name} +
+
+
+

Segments

+
+ {#each objectInfo.segments as segInfo (segInfo.name)} + + {/each} +
+
+
+

Symbols

+
+ {#if locallyUndefinedSymbols.length > 0} +

+ {locallyUndefinedSymbols.length} unresolved symbol(s)! +

+ {/if} + Provides: + {#each objectInfo.strongSymbols as sym} + + {/each} +
+ Requires: + {#each objectInfo.unresolvedSymbols as sym} + + {/each} +
+
+
+
diff --git a/frontend/src/patch_wizard/PatchMakerLogsView.svelte b/frontend/src/patch_wizard/PatchMakerLogsView.svelte new file mode 100644 index 000000000..18635a886 --- /dev/null +++ b/frontend/src/patch_wizard/PatchMakerLogsView.svelte @@ -0,0 +1,147 @@ + + + + +

PatchMaker Logs

+
+ {#each messages as message} + {#if message === linebreakMarker} +
+ {:else} +

{message}

+ {/if} + {/each} +
diff --git a/frontend/src/patch_wizard/PatchSymbol.svelte b/frontend/src/patch_wizard/PatchSymbol.svelte new file mode 100644 index 000000000..f39ac6d02 --- /dev/null +++ b/frontend/src/patch_wizard/PatchSymbol.svelte @@ -0,0 +1,51 @@ + + + + +{#if symbolInfo.providedBy?.length > 0} +

+ {symbolInfo.name} +

+{:else} +

+ {symbolInfo.name} +

+{/if} diff --git a/frontend/src/patch_wizard/RootWizardView.svelte b/frontend/src/patch_wizard/RootWizardView.svelte new file mode 100644 index 000000000..56cb1197a --- /dev/null +++ b/frontend/src/patch_wizard/RootWizardView.svelte @@ -0,0 +1,554 @@ + + + + + + + +
+ +

PATCH WIZARD

+
+ {#await patchInfoPromise} + + {:then _} +
+ + {#if patchInfo.userInputs.toolchain && overview.nSources} +

+ Using {patchInfo.userInputs.toolchain.split(".").pop()} to build + patch from + {overview.nSources} source code files. +

+ {:else if overview.nSources} +

No toolchain selected

+

to build {overview.nSources} source code files.

+ {:else if patchInfo.userInputs.toolchain} +

Using {patchInfo.userInputs.toolchain.split(".").pop()}

+

+ but no source code files to build patch from! +

+ {:else} +

+ No toolchain selected and no source code files provided! +

+ {/if} +
+ + +
+ + {#if overview.nSegments} +

+ {overview.nSegments} segment{overview.nSegments > 1 ? "s" : ""} totaling + 0x{overview.totalBytes.toString(16)} bytes will be extracted from + the compiled sources and injected into the target binary at unique + addresses. +

+ {:else} +

No segments chosen for injection!

+ {/if} + {#if overview.unallocatedSegments.length > 0} +

+ {overview.unallocatedSegments.length} segment(s) still need to be + allocated: +

+ {#each overview.unallocatedSegments as seg} +

{seg.unit}{seg.name}

+ {/each} + {/if} +
+ + +
+ + {#if patchInfo.symbolRefMap} +

+ Target binary provides {patchInfo.targetInfo.symbols.length} symbols: +

+ + {#each patchInfo.targetInfo.symbols as sym} + + {/each} + + {#if patchInfo.userInputs.symbols} +

+ You are providing {patchInfo.userInputs.symbols.length} symbol(s). +

+ {/if} + + {#if overview.unresolvedSyms.size > 0} + {#if overview.unresolvedSyms.size === 1} +

There as an unresolved symbol!

+ {:else} +

+ There are {overview.unresolvedSyms.size} unresolved symbols! +

+ {/if} + {#each Array.from(overview.unresolvedSyms) as sym} + + {/each} +

+ These symbols are referenced by the patch code, but not + defined in the patch code or the binary. +

+ {:else} +

There are no unresolved symbols.

+ {/if} + {/if} +
+ +
+
+ {/await} + +
+ +
+ +

{injectionStatus}

+
+ + {#await patchInfoPromise} + + {:then _} + + {/await} + +
+ + {#if subMenu} + + {/if} + +
diff --git a/frontend/src/patch_wizard/SegmentWidget.svelte b/frontend/src/patch_wizard/SegmentWidget.svelte new file mode 100644 index 000000000..4499e2328 --- /dev/null +++ b/frontend/src/patch_wizard/SegmentWidget.svelte @@ -0,0 +1,117 @@ + + + + +
+
+ {#if !lockInclude} + + {/if} + ({segmentInfo.unit}){segmentInfo.name} [{segmentInfo.permissions.toUpperCase()}] +
+
+ {#if segmentInfo.include} + [0x{segmentInfo.size.toString(16)} bytes] => + + {:else} + [0x{segmentInfo.size.toString(16)} bytes] (discarded) + {/if} +
+
diff --git a/frontend/src/patch_wizard/SourceMenuView.svelte b/frontend/src/patch_wizard/SourceMenuView.svelte new file mode 100644 index 000000000..f35e3e351 --- /dev/null +++ b/frontend/src/patch_wizard/SourceMenuView.svelte @@ -0,0 +1,93 @@ + + +
+ {#each patchInfo.sourceInfos as sourceInfo} + + {/each} + + +
diff --git a/frontend/src/patch_wizard/SourceWidget.svelte b/frontend/src/patch_wizard/SourceWidget.svelte new file mode 100644 index 000000000..93cd2c701 --- /dev/null +++ b/frontend/src/patch_wizard/SourceWidget.svelte @@ -0,0 +1,145 @@ + + + + +
+
+ + {#if showOptions} + + + + + + {:else} +

{sourceInfo.name}

+ + {/if} +
+ + {#if !sourceHidden} +
+ + +
+
+ + {title} + + {#if valid !== null} + + {#await updatePromise} + + {:then e} + + {:catch err} + + {/await} + + {/if} + + {#if markError} +

[ ! ]

+ {/if} +
+ {#if !collapsed} +
+ +
+ {/if} +
diff --git a/frontend/src/patch_wizard/SymbolView.svelte b/frontend/src/patch_wizard/SymbolView.svelte new file mode 100644 index 000000000..22e730f53 --- /dev/null +++ b/frontend/src/patch_wizard/SymbolView.svelte @@ -0,0 +1,154 @@ + + + + +
+ Define symbols which are required by the patch code and not provided by the + target binary. +
+ +
+ + {#if unresolvedSyms} + There are {unresolvedSyms.size} unresolved symbols. + {:else} + There are no unresolved symbols. + {/if} + {#if !undefinedSymsCollapse} +
+ {#each Array.from(unresolvedSyms) as sym} + + {/each} +
+ {/if} +
+ +
+
+ + + + + +
+ + {#each patchInfo.userInputs.symbols as [name, vaddr], idx} + + {/each} +
diff --git a/frontend/src/patch_wizard/ToolchainSetupView.svelte b/frontend/src/patch_wizard/ToolchainSetupView.svelte new file mode 100644 index 000000000..039c52d6a --- /dev/null +++ b/frontend/src/patch_wizard/ToolchainSetupView.svelte @@ -0,0 +1,67 @@ + + +{#await getToolchainList()} + +{:then toolchainConfig_structs} + + +{:catch err} + Failed to fetch toolchain configuration structure! +{/await} diff --git a/frontend/src/patch_wizard/UserInputSymbol.svelte b/frontend/src/patch_wizard/UserInputSymbol.svelte new file mode 100644 index 000000000..9d69ac50c --- /dev/null +++ b/frontend/src/patch_wizard/UserInputSymbol.svelte @@ -0,0 +1,67 @@ + + + + +
+ + + + + +
diff --git a/frontend/src/project/ProjectManagerToolbar.svelte b/frontend/src/project/ProjectManagerToolbar.svelte index 0dbbedc9f..692a4185d 100644 --- a/frontend/src/project/ProjectManagerToolbar.svelte +++ b/frontend/src/project/ProjectManagerToolbar.svelte @@ -1,10 +1,10 @@ @@ -46,7 +48,11 @@
- {@html hljs.highlight(script.join("\n"), { language: language }).value} + {#if language} + {@html hljs.highlight(script.join("\n"), { language: language }).value} + {:else} + {script.join("\n")} + {/if}
diff --git a/frontend/src/utils/SerializerInputForm.svelte b/frontend/src/utils/SerializerInputForm.svelte deleted file mode 100644 index 9f40efe1f..000000000 --- a/frontend/src/utils/SerializerInputForm.svelte +++ /dev/null @@ -1,353 +0,0 @@ - - - - -
-
- {#if node["name"] && !["builtins.bool", "builtins.str", "builtins.bytes", "builtins.int"].includes(node["type"])} - {nodeName} - {/if} - - - {#if node["type"] == "builtins.bool"} - - {nodeName} - - - - {:else if node["type"] == "builtins.str"} - - - - {:else if node["type"] == "builtins.bytes"} - - - - {:else if node["type"] == "builtins.int"} - - - - {:else if node["type"] == "typing.List" || node["type"] == "typing.Iterable"} -
- -
- {#each _element as elements} - {#if !skip.includes(elements)} -
-
- -
- -
- {/if} - {/each} - - - {:else if node["type"] == "typing.Tuple"} - {#each node["args"] as arg, i} - - {/each} - - - {:else if node["type"] == "typing.Dict"} -
- -
- {#each _element as elements, index} - {#if !skip.includes(elements)} -
-
- - {elements} -
-

Key

- -

Value

- -
- {/if} - {/each} - - - {:else if node["type"] == "typing.Union" || node["type"] == "typing.Optional"} - - {#if unionTypeSelect != null} - - {/if} - - - {:else if node["type"] == "ofrak.core.patch_maker.modifiers.SourceBundle"} - - - - {:else if node["enum"] != null} - - - - {:else if node["fields"] != null} - {#each node["fields"] as field, i} - {#if node["type"] == "ofrak_type.range.Range"} - - {:else} - - {/if} - {/each} - {/if} -
-
diff --git a/frontend/src/utils/Toolbar.svelte b/frontend/src/utils/Toolbar.svelte index cebcbaec5..b0387cbb6 100644 --- a/frontend/src/utils/Toolbar.svelte +++ b/frontend/src/utils/Toolbar.svelte @@ -61,6 +61,7 @@ button.shortcut.split('+').reverse().join(' + ') + ')' : '')}" + disabled="{button.disabled && button.disabled()}" > {#if button.iconUrl} diff --git a/frontend/src/utils/serializer_inputs/BaseSerializerInputForm.svelte b/frontend/src/utils/serializer_inputs/BaseSerializerInputForm.svelte new file mode 100644 index 000000000..32ebe9c7c --- /dev/null +++ b/frontend/src/utils/serializer_inputs/BaseSerializerInputForm.svelte @@ -0,0 +1,75 @@ + + + + +
+
+ {#if node.name && !["builtins.bool", "builtins.str", "builtins.bytes", "builtins.int"].includes(node.type)} + {nodeName} + {/if} + + +
+
diff --git a/frontend/src/utils/serializer_inputs/DictInputForm.svelte b/frontend/src/utils/serializer_inputs/DictInputForm.svelte new file mode 100644 index 000000000..c5931e06f --- /dev/null +++ b/frontend/src/utils/serializer_inputs/DictInputForm.svelte @@ -0,0 +1,63 @@ + + + + +
+ +
+{#each element as [subElementKey, subElementValue], index} +
+
+ +
+

Key

+ +

Value

+ +
+{/each} diff --git a/frontend/src/utils/serializer_inputs/EnumInputForm.svelte b/frontend/src/utils/serializer_inputs/EnumInputForm.svelte new file mode 100644 index 000000000..0eb32d6e7 --- /dev/null +++ b/frontend/src/utils/serializer_inputs/EnumInputForm.svelte @@ -0,0 +1,44 @@ + + + + + diff --git a/frontend/src/utils/serializer_inputs/ListInputForm.svelte b/frontend/src/utils/serializer_inputs/ListInputForm.svelte new file mode 100644 index 000000000..1eec0233e --- /dev/null +++ b/frontend/src/utils/serializer_inputs/ListInputForm.svelte @@ -0,0 +1,66 @@ + + + + +
+ +
+{#each element as subElement, i} +
+
+ +
+ +
+{/each} diff --git a/frontend/src/utils/serializer_inputs/ObjectInputForm.svelte b/frontend/src/utils/serializer_inputs/ObjectInputForm.svelte new file mode 100644 index 000000000..fcdfa45bd --- /dev/null +++ b/frontend/src/utils/serializer_inputs/ObjectInputForm.svelte @@ -0,0 +1,21 @@ + + +{#each node.fields as field, i} + +{/each} diff --git a/frontend/src/utils/serializer_inputs/PrimitivesInputForm.svelte b/frontend/src/utils/serializer_inputs/PrimitivesInputForm.svelte new file mode 100644 index 000000000..e20fd5644 --- /dev/null +++ b/frontend/src/utils/serializer_inputs/PrimitivesInputForm.svelte @@ -0,0 +1,117 @@ + + + + +{#if node.type === "builtins.bool"} + + {nodeName} + + + +{:else if node.type === "builtins.str"} + + + +{:else if node.type === "builtins.bytes"} + + + +{:else if node.type === "builtins.int"} + + + +{/if} diff --git a/frontend/src/utils/serializer_inputs/RangeInputForm.svelte b/frontend/src/utils/serializer_inputs/RangeInputForm.svelte new file mode 100644 index 000000000..a988a7510 --- /dev/null +++ b/frontend/src/utils/serializer_inputs/RangeInputForm.svelte @@ -0,0 +1,19 @@ + + +{#each node.fields as field, i} + +{/each} diff --git a/frontend/src/utils/serializer_inputs/SourceBundleInputForm.svelte b/frontend/src/utils/serializer_inputs/SourceBundleInputForm.svelte new file mode 100644 index 000000000..75921e648 --- /dev/null +++ b/frontend/src/utils/serializer_inputs/SourceBundleInputForm.svelte @@ -0,0 +1,28 @@ + + + diff --git a/frontend/src/utils/serializer_inputs/TupleInputForm.svelte b/frontend/src/utils/serializer_inputs/TupleInputForm.svelte new file mode 100644 index 000000000..350b465da --- /dev/null +++ b/frontend/src/utils/serializer_inputs/TupleInputForm.svelte @@ -0,0 +1,19 @@ + + +{#each node.args as arg, i} + +{/each} diff --git a/frontend/src/utils/serializer_inputs/UnionInputForm.svelte b/frontend/src/utils/serializer_inputs/UnionInputForm.svelte new file mode 100644 index 000000000..dc0c19060 --- /dev/null +++ b/frontend/src/utils/serializer_inputs/UnionInputForm.svelte @@ -0,0 +1,130 @@ + + + + +{#if node.type === "typing.Union"} + + {#if unionTypeSelect != null} + + {/if} +{:else if node.type === "typing.Optional"} + + {#if optionalSupplied} + + {/if} +{/if} diff --git a/frontend/src/views/ComponentsView.svelte b/frontend/src/views/ComponentsView.svelte index d91e349a0..98e237048 100644 --- a/frontend/src/views/ComponentsView.svelte +++ b/frontend/src/views/ComponentsView.svelte @@ -120,10 +120,10 @@ import { splitAndCapitalize } from "../helpers.js"; import { onMount } from "svelte"; - import SerializerInputForm from "../utils/SerializerInputForm.svelte"; import LoadingText from "../utils/LoadingText.svelte"; import Checkbox from "../utils/Checkbox.svelte"; import Button from "../utils/Button.svelte"; + import BaseSerializerInputForm from "../utils/serializer_inputs/BaseSerializerInputForm.svelte"; export let modifierView; let errorMessage, @@ -137,7 +137,7 @@ ofrakComponentsPromise = new Promise(() => {}), ofrakTargetsPromise = new Promise(() => {}), ofrakConfigsPromise = new Promise(() => {}), - config = {}, + config, ofrakConfigName = null; async function getTargetsAndComponents() { @@ -306,7 +306,7 @@ {:then ofrakConfig} {#if ofrakConfig.length != 0}

Configure {splitAndCapitalize(selectedComponent)}:

- diff --git a/frontend/src/views/RunScriptView.svelte b/frontend/src/views/RunScriptView.svelte index 6690d6123..e22f5d793 100644 --- a/frontend/src/views/RunScriptView.svelte +++ b/frontend/src/views/RunScriptView.svelte @@ -82,7 +82,6 @@