Skip to content

Commit

Permalink
Add Text editing and repacking; new mod repackage
Browse files Browse the repository at this point in the history
  • Loading branch information
cabalex committed Mar 7, 2024
1 parent 4be7b14 commit a890388
Show file tree
Hide file tree
Showing 20 changed files with 934 additions and 95 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ dist-ssr
*.sw?

maprenders/
old/
old/
TalkSubtitleMessage_USen.bin
8 changes: 7 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@sveltejs/svelte-virtual-list": "^3.0.1",
"@tabler/icons-svelte": "^2.47.0",
"blockly": "^10.4.1",
"encoding-japanese": "^2.0.0"
"encoding-japanese": "^2.0.0",
"fflate": "^0.8.2"
}
}
1 change: 1 addition & 0 deletions public/TalkSubtitleMessage_USen.json

Large diffs are not rendered by default.

Binary file added public/Text.pkz
Binary file not shown.
39 changes: 34 additions & 5 deletions src/GettingStarted/GettingStarted.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
<script lang="ts">
import { unzipSync } from "fflate";
import { IconFileImport, IconFilePlus } from "@tabler/icons-svelte";
import PlatinumFileReader from "../_lib/files/PlatinumFileReader";
import extract from "../_lib/files/DAT/extract";
import extractPKZ from "../_lib/files/PKZ/extract";
import extract_partial from "../_lib/files/PKZ/extract_partial";
import extractPTD from "../_lib/files/PTD/extract";
import Quest from "../_lib/Quest";
import { session, sessions } from "../store";
import { session, sessions, textCache } from "../store";
import QuestList from "./QuestList.svelte";
import QuestData from "../_lib/types/QuestData";
import EnemySet from "../_lib/types/EnemySet";
Expand All @@ -12,15 +16,40 @@
let inputElem: HTMLInputElement;
export let hidden: boolean = false;
function uploadFile(e: any) {
loadQuest({name: e.target.files[0].name, arrayBuffer: e.target.files[0]});
async function uploadFile(e: any) {
if (e.target.files[0].name.endsWith(".dat")) {
loadQuest({name: e.target.files[0].name, arrayBuffer: e.target.files[0]});
} else {
// zip file with Text in it
const unzipped = unzipSync(new Uint8Array(await new PlatinumFileReader(e.target.files[0]).read()));
console.log(unzipped);
const textPKZ = Object.keys(unzipped).find(f => f.endsWith("Text/Text.pkz"));
if (textPKZ) {
const extracted = await extractPKZ(new PlatinumFileReader(unzipped[textPKZ].buffer));
const text = extracted.files.find(f => f.name === "TalkSubtitleMessage_USen.bin");
if (text) {
const textArrayBuffer = (await extract_partial(text, extracted)).data;
$textCache = await extractPTD(new PlatinumFileReader(textArrayBuffer));
console.log("Extracted text file from ZIP", $textCache);
}
}
for (let [name, value] of Object.entries(unzipped)) {
if (name.endsWith(".dat")) {
loadQuest({name: name.split("/").pop(), arrayBuffer: value.buffer});
}
}
}
}
async function loadQuest(file: any) {
let reader = new PlatinumFileReader(file.arrayBuffer);
let result = await extract(reader);
try {
$sessions = [...$sessions, Quest.fromDAT(file.name, result)]
$sessions = [...$sessions, Quest.fromDAT(file.name, result, $textCache || undefined)]
$session = $sessions[$sessions.length - 1];
} catch (e: any) {
alert(`FAILED loading ${file.name}:\n${e.message}\n\nCheck console for more details.`);
Expand Down Expand Up @@ -48,7 +77,7 @@
<input
bind:this={inputElem}
on:change={uploadFile}
accept=".dat"
accept=".dat,.zip"
type="file"
style="display: none"
/>
Expand Down
30 changes: 27 additions & 3 deletions src/GettingStarted/QuestList.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<script lang="ts">
import { onMount } from "svelte";
import VirtualList from "@sveltejs/svelte-virtual-list";
import extract, { type FileData, type PartialFile } from "../_lib/files/PKZ/extract";
import extractPKZ, { type FileData, type PartialFile } from "../_lib/files/PKZ/extract";
import repackPTD from "../_lib/files/PTD/repack";
import extractPTD from "../_lib/files/PTD/extract";
import extract_partial from "../_lib/files/PKZ/extract_partial";
import PlatinumFileReader from "../_lib/files/PlatinumFileReader";
import { IconExclamationCircle, IconFile, IconSearch, IconX } from "@tabler/icons-svelte";
import Loading from "../assets/Loading.svelte";
import { questsCache } from "../store";
import { questsCache, textCache } from "../store";
import { lookup } from "../_lib/lookupTable";
export let onClick: (quest: {name: string, arrayBuffer: ArrayBuffer}) => void;
Expand All @@ -19,13 +21,34 @@
let response = await fetch('./quest.pkz');
if (!response.ok) throw new Error('Network response was not ok.');
let reader = new PlatinumFileReader(await response.arrayBuffer());
fileData = await extract(reader);
fileData = await extractPKZ(reader);
$questsCache = fileData;
} else {
fileData = $questsCache;
}
}
async function fetchText() {
if (!$textCache) {
let response = await fetch('./TalkSubtitleMessage_USen.json');
if (!response.ok) throw new Error('Network response was not ok.');
let json = await response.json();
// convert objects to maps
for (let [key, value] of Object.entries(json)) {
json[key] = new Map(Object.entries(value as {[key: string]: string[]}));
}
$textCache = {strings: json};
/*
// Legacy load directly from PTD - very slow!
let response = await fetch('./TalkSubtitleMessage_USen.bin');
if (!response.ok) throw new Error('Network response was not ok.');
let reader = new PlatinumFileReader(await response.arrayBuffer());
$textCache = await extractPTD(reader);
*/
}
}
function readableBytes(bytes: number) {
if (bytes == 0) {
return "0 B"
Expand All @@ -46,6 +69,7 @@
onMount(() => {
fetchQuests();
fetchText();
})
async function clickFile(partialFile: PartialFile) {
Expand Down
136 changes: 130 additions & 6 deletions src/IconBar/IconBar.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,82 @@
<script lang="ts">
import { IconPlus } from '@tabler/icons-svelte';
import { currentEm, currentTab, currentTask, session, sessions } from '../store';
import { IconPlus, IconCheck, IconPackages } from '@tabler/icons-svelte';
import { session, sessions, textCache } from '../store';
import Loading from '../assets/Loading.svelte';
import Icon from './Icon.svelte';
import repackPTD from "../_lib/files/PTD/repack";
import extractPKZ from "../_lib/files/PKZ/extract";
import extractPartialPKZ from "../_lib/files/PKZ/extract_partial";
import repackPKZ from "../_lib/files/PKZ/repack";
import PlatinumFileReader from "../_lib/files/PlatinumFileReader";
import { zipSync } from "fflate";
let packaging: null|string[] = null;
async function exportAllSessionsAsMod() {
const start = performance.now();
packaging = ["<b>[!] Exporting all sessions as a mod...</b>"];
await new Promise(r => setTimeout(r, 0));
const files: {[key: string]: any} = {};
for (let i = 0; i < $sessions.length; i++) {
packaging = [...packaging, `[+] Exporting quest <b>q${$sessions[i].id.toUpperCase()}</b>...`]
await new Promise(r => setTimeout(r, 0));
const file = await $sessions[i].repack();
if (file) {
files[`romfs/quest/quest${$sessions[i].id}.dat`] = new Uint8Array(file);
}
}
const finishedSessions = performance.now();
packaging = [...packaging, `[!] Finished exporting all sessions in ${Math.round(finishedSessions - start)}ms`];
// package text
if ($textCache) {
packaging = [...packaging, "[!] Repacking text file with modifications..."];
await new Promise(r => setTimeout(r, 0));
const repacked = await repackPTD($textCache);
packaging = [...packaging, "[!] Making PKZ..."];
let response = await fetch('./Text.pkz');
if (!response.ok) {
packaging = [...packaging, '<span style="color: var(--danger)">[-] ERROR: Network response was not ok. Stopping...</span>'];
return;
}
let reader = new PlatinumFileReader(await response.arrayBuffer());
const pkz = await extractPKZ(reader);
const pkzFiles = await Promise.all(pkz.files.map(f => extractPartialPKZ(f, pkz)));
let fileToReplace = pkzFiles.find(f => f.name == "TalkSubtitleMessage_USen.bin");
if (fileToReplace) {
fileToReplace.data = new Uint8Array(repacked);
} else {
packaging = [...packaging, '<span style="color: var(--danger)">[-] ERROR: Couldn\'t find TalkSubtitleMessage_USen.bin in Text.pkz! This error shouldn\'t occur...</span>'];
console.error("Couldn't find TalkSubtitleMessage_USen.bin in Text.pkz!");
}
files["romfs/Text/Text.pkz"] = new Uint8Array(await repackPKZ(pkzFiles, "ZStandard"));
const finishedRepackingText = performance.now();
packaging = [...packaging, `[!] Finished repacking text in ${Math.round(finishedRepackingText - finishedSessions)}ms`];
}
packaging = [...packaging, "[!] Zipping..."];
const zipped = zipSync(files);
let blob = new Blob([zipped], { type: "application/octet-stream" });
let url = URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `mod-complete.zip`;
a.click();
URL.revokeObjectURL(url);
const finishedZipping = performance.now();
packaging = [...packaging, `[!] Mod packaging complete! :D It took ${Math.round(finishedZipping - start)}ms.`];
await new Promise(r => setTimeout(r, 3000));
packaging = null;
}
</script>

<div class="iconBar">
Expand All @@ -15,12 +90,41 @@
>
<IconPlus />
</button>
{#if $sessions.length > 0}
<div style="flex-grow: 1" />
<hr />
<button
class="session"
title="Export all sessions as mod"
class:active={packaging !== null}
on:click={exportAllSessionsAsMod}
>
{#if packaging !== null && packaging[packaging.length - 1].startsWith("[!] Mod packaging complete! :D")}
<IconCheck />
{:else if packaging !== null}
<Loading text="" />
{:else}
<IconPackages />
{/if}
</button>
{/if}
</div>

{#if packaging !== null}
<div class="iconTooltip" style="bottom: -5px">
<div class="log">
{#each packaging as logItem}
<p>{@html logItem}</p>
{/each}
</div>
</div>
{/if}

<style>
.iconBar {
padding: 10px;
height: calc(100% - 20px);
padding: 0 10px;
padding-top: 10px;
height: calc(100% - 10px);
border-right: 1px solid #444;
background-color: #111;
z-index: 12;
Expand Down Expand Up @@ -57,13 +161,33 @@
cursor: default;
}
.iconTooltip {
pointer-events: none;
position: fixed;
left: 70px;
transform: translateY(-20px);
z-index: 15;
background-color: #222;
background-color: #111;
padding: 5px;
border-radius: 5px;
font-weight: bold;
}
.iconTooltip:before {
content: "";
position: absolute;
bottom: 12px;
left: -10px;
transform: rotate(90deg);
border: 5px solid transparent;
border-top-color: #111;
}
.log {
width: 500px;
max-height: 500px;
overflow-y: auto;
}
.log p {
margin: 0;
font-weight: normal;
font-family: monospace;
font-size: 18px;
}
</style>

0 comments on commit a890388

Please sign in to comment.