diff --git a/deps/je2be-core b/deps/je2be-core index 8eb12c4..6fbc2bf 160000 --- a/deps/je2be-core +++ b/deps/je2be-core @@ -1 +1 @@ -Subproject commit 8eb12c473f50d296fdeeb5b195321cf1787e7a62 +Subproject commit 6fbc2bf81944e5dab89cd118ca2c6b29d8d7314f diff --git a/src/front/component/convert.tsx b/src/front/component/convert.tsx index a732f29..f375649 100644 --- a/src/front/component/convert.tsx +++ b/src/front/component/convert.tsx @@ -4,7 +4,7 @@ import { v4 as uuidv4 } from "uuid"; import { ConvertSession } from "../convert-session"; import { gettext } from "../i18n"; import { useForceUpdate } from "./main"; -import { WorkerError } from "../../share/messages"; +import { WorkerError, WorkerErrorType } from "../../share/messages"; import { ErrorMessage } from "./error-message"; import { directoryNameFromFileList } from "../../share/file-list-ext"; import { @@ -19,6 +19,7 @@ import { convertModeDescription, convertModeInputFileExtension, convertModeMetadata, + convertModeRequiredFile, convertModeSupportsDirectoryInput, } from "../mode"; import { Link } from "./link"; @@ -70,7 +71,17 @@ export const Convert: React.FC<{ session.current?.close(); onBack(); }; - const changeHandler = ({ file }: { file: boolean }) => { + const changeHandler = ({ + file, + requiredFile, + }: { + file: boolean; + requiredFile?: { + name: string; + notFoundError: WorkerErrorType; + tooManyError: WorkerErrorType; + }; + }) => { return (ev: ChangeEvent) => { const files = ev.target.files; const noFileSelectedError: WorkerError = { @@ -98,7 +109,7 @@ export const Convert: React.FC<{ source = f; filename = f.name; } else { - let numLevelDatFiles = 0; + let numRequiredFiles = 0; const dirname = directoryNameFromFileList(files); if (dirname === undefined) { setState({ @@ -109,22 +120,28 @@ export const Convert: React.FC<{ }); return; } - for (let i = 0; i < files.length; i++) { - const item = files.item(i); - if (!item) { - continue; + if (requiredFile !== undefined) { + for (let i = 0; i < files.length; i++) { + const item = files.item(i); + if (!item) { + continue; + } + if (item.name === requiredFile.name) { + numRequiredFiles++; + } } - if (item.name === "level.dat") { - numLevelDatFiles++; + if (numRequiredFiles === 0) { + setState({ + error: { type: requiredFile.notFoundError, native: {} }, + }); + return; + } else if (numRequiredFiles > 1) { + setState({ + error: { type: requiredFile.tooManyError, native: {} }, + }); + return; } } - if (numLevelDatFiles === 0) { - setState({ error: { type: "NoLevelDatFound", native: {} } }); - return; - } else if (numLevelDatFiles > 1) { - setState({ error: { type: "2OrMoreLevelDatFound", native: {} } }); - return; - } source = files; filename = dirname; } @@ -175,7 +192,10 @@ export const Convert: React.FC<{ {gettext("Mode: ") + convertModeDescription(mode)}
- {convertModeSupportsDirectoryInput(mode) && ( + {(mode === "j2b" || + mode === "b2j" || + mode === "p2j" || + mode === "p2b") && (
{gettext( - "Select a world directory to convert, which must contain a level.dat file", + mode === "j2b" || mode === "b2j" + ? "Select a world directory to convert, which must contain a level.dat file" + : "Select a world directory to convert, which must contain a GAMEDATA file", )}
@@ -304,6 +329,8 @@ export const Convert: React.FC<{ const FileInputGuidance: React.FC<{ mode: ConvertMode }> = ({ mode }) => { switch (mode) { case "j2b": + case "p2j": + case "p2b": return ( <>{gettext("Select a zip archive of world directory to convert")} ); diff --git a/src/front/component/mode-select.tsx b/src/front/component/mode-select.tsx index ab08d01..dcbd7db 100644 --- a/src/front/component/mode-select.tsx +++ b/src/front/component/mode-select.tsx @@ -34,6 +34,18 @@ export const ModeSelect: React.FC<{ > {gettext("Xbox360 to Java")}
+
onSelect("p2b")} + > + {gettext("PS3 to Bedrock")} +
+
onSelect("p2j")} + > + {gettext("PS3 to Java")} +
); }; diff --git a/src/front/i18n.ts b/src/front/i18n.ts index d86ea9c..65492ab 100644 --- a/src/front/i18n.ts +++ b/src/front/i18n.ts @@ -67,13 +67,19 @@ export function gettext( case "Xbox360 to Java": return "Xbox360版 から Java版に"; case "Select a world directory to convert, which must contain a level.dat file": - return "変換するワールドのディレクトリを選択して下さい。このディレクトリには level.dat が含まれている必要があります"; + return "変換するワールドのディレクトリを選択して下さい。このディレクトリには level.dat ファイルが含まれている必要があります"; case "Select a zip archive of world directory to convert": - return "変換するワールドの zip ファイルを選択して下さい"; + return "変換するワールドのディレクトリを zip に圧縮したファイルを選択して下さい"; case "Select a *.mcworld file to convert": return "変換する *.mcworld ファイルを選択して下さい"; case "Select a *.bin to convert. This file can be copied from Xbox360 using USB stick. Check the link to know how to prepare USB stick for Xbox360": return "変換する *.bin ファイルを選択して下さい。このファイルは USB メモリーを使うことで Xbox360 の実機からコピーする必要があります。Xbox360 用の USB メモリーを準備する方法は以下のリンクが参考になります。"; + case "PS3 to Java": + return "PS3版 から Java版に"; + case "PS3 to Bedrock": + return "PS3版 から 統合版に"; + case "Select a world directory to convert, which must contain a GAMEDATA file": + return "変換するワールドのディレクトリを選択して下さい。このディレクトリには GAMEDATA ファイルが含まれている必要があります"; } break; } diff --git a/src/front/mode.ts b/src/front/mode.ts index 566726b..5293230 100644 --- a/src/front/mode.ts +++ b/src/front/mode.ts @@ -2,21 +2,26 @@ import { B2JConverterMetadata, ConverterMetadata, J2BConverterMetadata, + P2BConverterMetadata, + P2JConverterMetadata, X2BConverterMetadata, X2JConverterMetadata, } from "../share/progress"; import { gettext } from "./i18n"; +import { WorkerError, WorkerErrorType } from "../share/messages"; -export type ConvertMode = "j2b" | "b2j" | "x2b" | "x2j"; +export type ConvertMode = "j2b" | "b2j" | "x2b" | "x2j" | "p2b" | "p2j"; export type Mode = "select" | ConvertMode; export function convertModeSupportsDirectoryInput(mode: ConvertMode): boolean { - return mode === "j2b" || mode == "b2j"; + return mode === "j2b" || mode === "b2j" || mode === "p2b" || mode === "p2j"; } export function convertModeInputFileExtension(mode: ConvertMode): string { switch (mode) { case "j2b": + case "p2b": + case "p2j": return ".zip"; case "b2j": return ".mcworld"; @@ -30,13 +35,42 @@ export function convertModeOutputFileExtension(mode: ConvertMode): string { switch (mode) { case "j2b": case "x2b": + case "p2b": return ".mcworld"; case "b2j": case "x2j": + case "p2j": return ".zip"; } } +export function convertModeRequiredFile(mode: ConvertMode): + | { + name: string; + notFoundError: WorkerErrorType; + tooManyError: WorkerErrorType; + } + | undefined { + switch (mode) { + case "j2b": + case "b2j": + return { + name: "level.dat", + notFoundError: "NoLevelDatFound", + tooManyError: "2OrMoreLevelDatFound", + }; + case "p2j": + case "p2b": + return { + name: "GAMEDATA", + notFoundError: "NoGAMEDATAFound", + tooManyError: "2OrMoreGAMEDATAFound", + }; + default: + return undefined; + } +} + export function convertModeMetadata( mode: ConvertMode, file: boolean, @@ -50,6 +84,10 @@ export function convertModeMetadata( return new X2JConverterMetadata(); case "x2b": return new X2BConverterMetadata(); + case "p2b": + return new P2BConverterMetadata(file); + case "p2j": + return new P2JConverterMetadata(file); } } @@ -63,5 +101,9 @@ export function convertModeDescription(mode: ConvertMode): string { return gettext("Xbox360 to Java"); case "x2b": return gettext("Xbox360 to Bedrock"); + case "p2b": + return gettext("PS3 to Bedrock"); + case "p2j": + return gettext("PS3 to Java"); } } diff --git a/src/front/translation-key.ts b/src/front/translation-key.ts index c718498..1697497 100644 --- a/src/front/translation-key.ts +++ b/src/front/translation-key.ts @@ -26,4 +26,7 @@ export type TranslationKey = | "Unsupported browser because:" | "Xbox360 to Bedrock" | "Xbox360 to Java" - | "seconds"; + | "seconds" + | "PS3 to Bedrock" + | "PS3 to Java" + | "Select a world directory to convert, which must contain a GAMEDATA file"; diff --git a/src/share/messages.ts b/src/share/messages.ts index 27517a4..964c50b 100644 --- a/src/share/messages.ts +++ b/src/share/messages.ts @@ -27,9 +27,11 @@ export function isProgressMessage(x: any): x is ProgressMessage { ); } -type WorkerErrorType = +export type WorkerErrorType = | "NoLevelDatFound" + | "NoGAMEDATAFound" | "2OrMoreLevelDatFound" + | "2OrMoreGAMEDATAFound" | "ConverterFailed" | "Unzip" | "CopyToIdb" diff --git a/src/share/progress.ts b/src/share/progress.ts index 53a0cb6..eaf49e3 100644 --- a/src/share/progress.ts +++ b/src/share/progress.ts @@ -156,6 +156,84 @@ export class X2BConverterMetadata implements ConverterMetadata { } } +export class P2JConverterMetadata implements ConverterMetadata { + constructor(readonly file: boolean) {} + + get steps(): Step[] { + if (this.file) { + return ["unzip", "extract"]; + } else { + return ["copy", "extract"]; + } + } + + displayUnit(step: Step): string | undefined { + switch (step) { + case "copy": + case "unzip": + return "files"; + case "extract": + return undefined; + } + } + + stepDescription(step: Step): string | undefined { + switch (step) { + case "copy": + return "Copy"; + case "unzip": + return "Unzip"; + case "extract": + return "Convert"; + } + } +} + +export class P2BConverterMetadata implements ConverterMetadata { + constructor(readonly file: boolean) {} + + get steps(): Step[] { + if (this.file) { + return ["unzip", "extract", "convert", "postprocess", "compaction"]; + } else { + return ["copy", "extract", "convert", "postprocess", "compaction"]; + } + } + + displayUnit(step: Step): string | undefined { + switch (step) { + case "copy": + case "unzip": + return "files"; + case "extract": + return undefined; + case "convert": + return "chunks"; + case "postprocess": + return undefined; + case "compaction": + return undefined; + } + } + + stepDescription(step: Step): string | undefined { + switch (step) { + case "copy": + return "Copy"; + case "unzip": + return "Unzip"; + case "extract": + return "Extract"; + case "convert": + return "Convert"; + case "postprocess": + return "Post Process"; + case "compaction": + return "LevelDB Compaction"; + } + } +} + export function nextProgress( progress: Progress, m: ProgressMessage, diff --git a/src/worker/dedicated/converter.ts b/src/worker/dedicated/converter.ts index eb04b7f..b291f11 100644 --- a/src/worker/dedicated/converter.ts +++ b/src/worker/dedicated/converter.ts @@ -118,9 +118,13 @@ async function start(m: StartMessage): Promise { await db.dlFiles.clear(); let inputPath: string; - if (mode === "j2b" || mode === "b2j") { + if (mode === "j2b" || mode === "b2j" || mode === "p2j" || mode === "p2b") { console.log(`[converter] (${id}) extract...`); - await extract(file, id); + await extract( + file, + id, + mode === "j2b" || mode === "b2j" ? "level.dat" : "GAMEDATTA", + ); console.log(`[converter] (${id}) extract done`); inputPath = `/je2be/${id}/in`; } else { @@ -155,6 +159,12 @@ async function start(m: StartMessage): Promise { case "x2b": errorJsonPtr = Module._Xbox360ToBedrock(inputPtr, outputPtr, idPtr); break; + case "p2b": + errorJsonPtr = Module._PS3ToBedrock(inputPtr, outputPtr, idPtr); + break; + case "p2j": + errorJsonPtr = Module._PS3ToJava(inputPtr, outputPtr, idPtr); + break; } if (errorJsonPtr != 0) { const errorJsonString = UTF8ToString(errorJsonPtr); @@ -190,15 +200,23 @@ async function start(m: StartMessage): Promise { self.postMessage(done); } -async function extract(file: File | FileList, id: string): Promise { +async function extract( + file: File | FileList, + id: string, + requiredFile: string, +): Promise { if (file instanceof File) { - await extractZip(file, id); + await extractZip(file, id, requiredFile); } else { - await copyDirectory(file, id); + await copyDirectory(file, id, requiredFile); } } -async function copyDirectory(file: FileList, id: string): Promise { +async function copyDirectory( + file: FileList, + id: string, + requiredFile: string, +): Promise { let prefix = directoryNameFromFileList(file); if (prefix === undefined) { const e: WorkerError = { @@ -248,7 +266,11 @@ async function copyDirectory(file: FileList, id: string): Promise { await Promise.all(promises); } -async function extractZip(file: File, id: string): Promise { +async function extractZip( + file: File, + id: string, + requiredFile: string, +): Promise { let zip: any; try { zip = await JSZip.loadAsync(file); @@ -263,21 +285,25 @@ async function extractZip(file: File, id: string): Promise { }; throw error; } - const foundLevelDat: string[] = []; + const foundRequired: string[] = []; zip.forEach((p: string) => { - if (p === "level.dat" || p.endsWith("/level.dat")) { - foundLevelDat.push(p); + if (p === requiredFile || p.endsWith(`/${requiredFile}`)) { + foundRequired.push(p); } }); - if (foundLevelDat.length === 0) { + if (foundRequired.length === 0) { const error: WorkerError = { - type: "NoLevelDatFound", + type: + requiredFile === "level.dat" ? "NoLevelDatFound" : "NoGAMEDATAFound", native: {}, }; throw error; - } else if (foundLevelDat.length !== 1) { + } else if (foundRequired.length !== 1) { const error: WorkerError = { - type: "2OrMoreLevelDatFound", + type: + requiredFile === "level.dat" + ? "2OrMoreLevelDatFound" + : "2OrMoreGAMEDATAFound", native: {}, }; throw error; @@ -292,9 +318,9 @@ async function extractZip(file: File, id: string): Promise { }; self.postMessage(m); - const levelDatPath = foundLevelDat[0]; - const idx = levelDatPath.lastIndexOf("level.dat"); - const prefix = levelDatPath.substring(0, idx); + const requiredFilePath = foundRequired[0]; + const idx = requiredFilePath.lastIndexOf(requiredFile); + const prefix = requiredFilePath.substring(0, idx); const files: string[] = []; zip.forEach((path: string, f: JSZip.JSZipObject) => { const normal = path.replace("\\", "/"); diff --git a/src/worker/dedicated/core.cpp b/src/worker/dedicated/core.cpp index 6895394..79777ec 100644 --- a/src/worker/dedicated/core.cpp +++ b/src/worker/dedicated/core.cpp @@ -99,7 +99,7 @@ je2be::Status Error(char const *file, int line, std::string what = {}) { return je2be::Status(je2be::Status::ErrorData(w, what)); } -struct J2BProgress : public je2be::tobe::Progress { +struct J2BProgress : public je2be::java::Progress { explicit J2BProgress(std::string const &id) : fId(id) {} bool reportConvert(je2be::Rational const &progress, uint64_t numConvertedChunks) override { @@ -120,7 +120,7 @@ struct J2BProgress : public je2be::tobe::Progress { std::string fId; }; -struct B2JProgress : public je2be::toje::Progress { +struct B2JProgress : public je2be::bedrock::Progress { explicit B2JProgress(std::string const &id) : fId(id) {} bool reportConvert(je2be::Rational const &progress, uint64_t numConvertedChunks) override { @@ -136,8 +136,8 @@ struct B2JProgress : public je2be::toje::Progress { std::string fId; }; -struct X2JProgress : public je2be::box360::Progress { - explicit X2JProgress(std::string const &id) : fId(id) {} +struct LCEProgress : public je2be::lce::Progress { + explicit LCEProgress(std::string const &id) : fId(id) {} bool report(je2be::Rational const &progress) override { PostProgressMessage(fId, "extract", progress.toD(), 0); @@ -150,7 +150,7 @@ struct X2JProgress : public je2be::box360::Progress { je2be::Status UnsafeJavaToBedrock(char *input, char *output, char *id) { using namespace std; using namespace je2be; - using namespace je2be::tobe; + using namespace je2be::java; Options options; options.fLevelDirectoryStructure = LevelDirectoryStructure::Vanilla; @@ -171,7 +171,7 @@ je2be::Status UnsafeJavaToBedrock(char *input, char *output, char *id) { je2be::Status UnsafeBedrockToJava(char *input, char *output, char *id) { using namespace std; using namespace je2be; - using namespace je2be::toje; + using namespace je2be::bedrock; Options options; #if J2B_DEBUG_CHUNK_FILTER_RADIUS @@ -191,10 +191,10 @@ je2be::Status UnsafeBedrockToJava(char *input, char *output, char *id) { je2be::Status UnsafeXbox360ToJava(char *input, char *output, char *id) { using namespace std; using namespace je2be; - using namespace je2be::box360; + using namespace je2be::xbox360; int concurrency = (int)thread::hardware_concurrency() - 1; - Options options; + lce::Options options; #if J2B_DEBUG_CHUNK_FILTER_RADIUS int r = J2B_DEBUG_CHUNK_FILTER_RADIUS; for (int x = -r; x <= r; x++) { @@ -203,7 +203,7 @@ je2be::Status UnsafeXbox360ToJava(char *input, char *output, char *id) { } } #endif - X2JProgress progress(id); + LCEProgress progress(id); return Converter::Run(fs::path(input), fs::path(output), concurrency, options, &progress); } @@ -219,7 +219,7 @@ je2be::Status UnsafeXbox360ToBedrock(char *input, char *output, char *id) { int concurrency = (int)thread::hardware_concurrency() - 1; { - box360::Options options; + lce::Options options; #if J2B_DEBUG_CHUNK_FILTER_RADIUS int r = J2B_DEBUG_CHUNK_FILTER_RADIUS; for (int x = -r; x <= r; x++) { @@ -228,15 +228,15 @@ je2be::Status UnsafeXbox360ToBedrock(char *input, char *output, char *id) { } } #endif - X2JProgress progress(id); - auto st = box360::Converter::Run(fs::path(input), javaOutput, concurrency, options, &progress); + LCEProgress progress(id); + auto st = xbox360::Converter::Run(fs::path(input), javaOutput, concurrency, options, &progress); if (!st.ok()) { return st; } } { - tobe::Options options; + java::Options options; #if J2B_DEBUG_CHUNK_FILTER_RADIUS int r = J2B_DEBUG_CHUNK_FILTER_RADIUS; for (int x = -r; x <= r; x++) { @@ -246,7 +246,69 @@ je2be::Status UnsafeXbox360ToBedrock(char *input, char *output, char *id) { } #endif J2BProgress progress(id); - return tobe::Converter::Run(javaOutput, fs::path(output), options, concurrency, &progress); + return java::Converter::Run(javaOutput, fs::path(output), options, concurrency, &progress); + } +} + +je2be::Status UnsafePS3ToJava(char *input, char *output, char *id) { + using namespace std; + using namespace je2be; + using namespace je2be::ps3; + + int concurrency = (int)thread::hardware_concurrency() - 1; + lce::Options options; +#if J2B_DEBUG_CHUNK_FILTER_RADIUS + int r = J2B_DEBUG_CHUNK_FILTER_RADIUS; + for (int x = -r; x <= r; x++) { + for (int z = -r; z <= r; z++) { + options.fChunkFilter.insert(je2be::Pos2i(x, z)); + } + } +#endif + LCEProgress progress(id); + return Converter::Run(fs::path(input), fs::path(output), concurrency, options, &progress); +} + +je2be::Status UnsafePS3ToBedrock(char *input, char *output, char *id) { + using namespace std; + using namespace je2be; + + auto javaOutput = fs::temp_directory_path() / "java"; + if (!je2be::Fs::CreateDirectories(javaOutput)) { + return Error(__FILE__, __LINE__, "can't create directory " + javaOutput.string()); + } + + int concurrency = (int)thread::hardware_concurrency() - 1; + + { + lce::Options options; +#if J2B_DEBUG_CHUNK_FILTER_RADIUS + int r = J2B_DEBUG_CHUNK_FILTER_RADIUS; + for (int x = -r; x <= r; x++) { + for (int z = -r; z <= r; z++) { + options.fChunkFilter.insert(je2be::Pos2i(x, z)); + } + } +#endif + LCEProgress progress(id); + auto st = ps3::Converter::Run(fs::path(input), javaOutput, concurrency, options, &progress); + if (!st.ok()) { + return st; + } + } + + { + java::Options options; +#if J2B_DEBUG_CHUNK_FILTER_RADIUS + int r = J2B_DEBUG_CHUNK_FILTER_RADIUS; + for (int x = -r; x <= r; x++) { + for (int z = -r; z <= r; z++) { + options.fChunkFilter.insert(je2be::Pos2i(x, z)); + } + } +#endif + J2BProgress progress(id); + return java::Converter::Run(javaOutput, fs::path(output), options, concurrency, &progress); } } @@ -290,3 +352,11 @@ J2B_EXPORT char *Xbox360ToJava(char *input, char *output, char *id) { J2B_EXPORT char *Xbox360ToBedrock(char *input, char *output, char *id) { return Wrap(UnsafeXbox360ToBedrock, input, output, id); } + +J2B_EXPORT char *PS3ToJava(char *input, char *output, char *id) { + return Wrap(UnsafePS3ToJava, input, output, id); +} + +J2B_EXPORT char *PS3ToBedrock(char *input, char *output, char *id) { + return Wrap(UnsafePS3ToBedrock, input, output, id); +}