Compare commits
2 Commits
6e7136aaea
...
b459c60d5b
Author | SHA1 | Date |
---|---|---|
Les De Ridder | b459c60d5b | |
Les De Ridder | 1b83e18cc4 |
|
@ -14,3 +14,5 @@ cm3d2tool-test-*
|
|||
*.obj
|
||||
*.lst
|
||||
out/
|
||||
*.menu
|
||||
*.txt
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "dfuse"]
|
||||
path = dfuse
|
||||
url = https://github.com/lesderid/dfuse
|
|
@ -0,0 +1 @@
|
|||
Subproject commit d92184e5ca90730a2aceef469a393eeed9e9c5c7
|
1
dub.sdl
1
dub.sdl
|
@ -4,4 +4,5 @@ authors "lesderid"
|
|||
copyright "Copyright © 2019, Les De Ridder"
|
||||
license "NCSA"
|
||||
dependency "sdlang-d" version="~>0.10.4"
|
||||
dependency "dfuse" version="~>0.3.0" path="dfuse"
|
||||
targetPath "out"
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
{
|
||||
"fileVersion": 1,
|
||||
"versions": {
|
||||
"dfuse": {"path":"dfuse"},
|
||||
"libinputvisitor": "1.2.2",
|
||||
"sdlang-d": "0.10.4",
|
||||
"taggedalgebraic": "0.10.12",
|
||||
"sdlang-d": "0.10.5",
|
||||
"taggedalgebraic": "0.11.2",
|
||||
"unit-threaded": "0.7.55"
|
||||
}
|
||||
}
|
||||
|
|
119
source/app.d
119
source/app.d
|
@ -1,51 +1,102 @@
|
|||
import std.stdio;
|
||||
import std.range;
|
||||
import std.algorithm;
|
||||
import std.path;
|
||||
import std.file;
|
||||
|
||||
import dfuse.fuse;
|
||||
|
||||
import cm3d2;
|
||||
|
||||
void main(string[] args)
|
||||
{
|
||||
if (args.length < 2)
|
||||
{
|
||||
help(args[0]);
|
||||
return;
|
||||
}
|
||||
if (args.length < 2)
|
||||
{
|
||||
help(args[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (args[1])
|
||||
{
|
||||
case "help":
|
||||
case "-h":
|
||||
case "--help":
|
||||
help(args[0]);
|
||||
break;
|
||||
case "menu-to-sdl":
|
||||
ubyte[] data = stdin.byChunk(4096).joiner.array;
|
||||
writeln(Menu.fromMenu(data).toSDL());
|
||||
break;
|
||||
case "sdl-to-menu":
|
||||
throw new Exception("Not implemented yet");
|
||||
default:
|
||||
writeln("Unknown option '" ~ args[1] ~ "'");
|
||||
writeln();
|
||||
goto case "help";
|
||||
}
|
||||
switch (args[1])
|
||||
{
|
||||
case "help":
|
||||
case "-h":
|
||||
case "--help":
|
||||
help(args[0]);
|
||||
break;
|
||||
case "menu-to-sdl":
|
||||
ubyte[] data = stdin.byChunk(4096).joiner.array;
|
||||
writeln(Menu.fromMenu(data).toSDL());
|
||||
break;
|
||||
case "sdl-to-menu":
|
||||
throw new Exception("Not implemented yet");
|
||||
case "mount-arc":
|
||||
string arcPath;
|
||||
string mountpoint;
|
||||
|
||||
if (args.length < 4 || !(arcPath = args[2]).isValidPath || !(mountpoint = args[3]).isValidPath)
|
||||
{
|
||||
goto case "help";
|
||||
}
|
||||
|
||||
mountArc(arcPath, mountpoint);
|
||||
break;
|
||||
default:
|
||||
writeln("Unknown option '" ~ args[1] ~ "'");
|
||||
writeln();
|
||||
goto case "help";
|
||||
}
|
||||
}
|
||||
|
||||
void help(string toolPath)
|
||||
{
|
||||
writeln("Usage: " ~ toolPath ~ " <command>");
|
||||
writeln();
|
||||
writeln("usage: " ~ toolPath ~ " [--help] <command> [<args>]");
|
||||
writeln();
|
||||
|
||||
void showCommandUsage(string command, string description)
|
||||
{
|
||||
writeln(" " ~ toolPath ~ " " ~ command);
|
||||
writeln(" " ~ description);
|
||||
}
|
||||
void showCommandUsage(string command, string description)
|
||||
{
|
||||
writeln(" " ~ toolPath ~ " " ~ command);
|
||||
writeln(" " ~ description);
|
||||
}
|
||||
|
||||
showCommandUsage("menu-to-sdl", "Convert .menu to SDL");
|
||||
showCommandUsage("sdl-to-menu", "Convert SDL to .menu");
|
||||
showCommandUsage("menu-to-sdl", "Convert .menu (from stdin) to SDL (stdout)");
|
||||
showCommandUsage("sdl-to-menu", "Convert SDL (from stdin) to .menu (stdout)");
|
||||
writeln();
|
||||
showCommandUsage("mount-arc <path> <mountpoint>", "FUSE mount arc file(s) on mountpoint");
|
||||
|
||||
writeln();
|
||||
writeln("Reads input from stdin and writes output to stdout.");
|
||||
writeln();
|
||||
writeln("Reads input from stdin and writes output to stdout.");
|
||||
}
|
||||
|
||||
void mountArc(string arcPath, string mountpoint)
|
||||
{
|
||||
if (!arcPath.exists)
|
||||
{
|
||||
writeln("Error: .arc path doesn't exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mountpoint.exists || !mountpoint.isDir)
|
||||
{
|
||||
writeln("Error: mountpoint doesn't exist or isn't a directory.");
|
||||
return;
|
||||
}
|
||||
|
||||
string[] arcFiles;
|
||||
if (arcPath.isDir)
|
||||
{
|
||||
arcFiles ~= arcPath.dirEntries("*.arc", SpanMode.shallow).map!(e => e.name).array;
|
||||
}
|
||||
else
|
||||
{
|
||||
arcFiles ~= arcPath;
|
||||
}
|
||||
|
||||
if (arcFiles.length == 0)
|
||||
{
|
||||
writeln("Error: no .arc file(s) selected!");
|
||||
return;
|
||||
}
|
||||
|
||||
auto fuse = new Fuse("cm3d2tool-arcfs", true, true);
|
||||
fuse.mount(new ArcFileSystem(arcFiles), mountpoint, ["ro", "noexec"]);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
module cm3d2.arc;
|
||||
|
||||
import std.file;
|
||||
import std.mmfile;
|
||||
|
||||
import dfuse.fuse;
|
||||
|
||||
import cm3d2;
|
||||
|
||||
class WarcFile
|
||||
{
|
||||
private MmFile _file;
|
||||
|
||||
this(MmFile file)
|
||||
{
|
||||
_file = file;
|
||||
|
||||
readHeader();
|
||||
}
|
||||
|
||||
private void readHeader()
|
||||
{
|
||||
auto data = cast(ubyte[]) _file[];
|
||||
|
||||
auto type = cast(string) data.readBytes(4);
|
||||
assert(type == "warc", "Invalid warc file: " ~ type);
|
||||
}
|
||||
}
|
||||
|
||||
class WarpFile
|
||||
{
|
||||
private MmFile _file;
|
||||
|
||||
this(MmFile file)
|
||||
{
|
||||
_file = file;
|
||||
|
||||
readHeader();
|
||||
}
|
||||
|
||||
private void readHeader()
|
||||
{
|
||||
auto data = cast(ubyte[]) _file[];
|
||||
|
||||
auto type = cast(string) data.readBytes(4);
|
||||
assert(type == "warp", "Invalid warp file: " ~ type);
|
||||
}
|
||||
}
|
||||
|
||||
class ArcFileSystem : Operations
|
||||
{
|
||||
private string[] _arcPaths;
|
||||
private WarcFile[] _warcFiles;
|
||||
private WarpFile[] _warpFiles;
|
||||
|
||||
this(string[] arcPaths)
|
||||
{
|
||||
import std.stdio;
|
||||
|
||||
foreach (path; arcPaths)
|
||||
{
|
||||
assert(path.exists);
|
||||
|
||||
stderr.write(path ~ ": ");
|
||||
|
||||
auto mmfile = new MmFile(path);
|
||||
auto type = cast(string) mmfile[0 .. 4];
|
||||
if (type == "warc")
|
||||
{
|
||||
_warcFiles ~= new WarcFile(mmfile);
|
||||
|
||||
stderr.writeln("warc");
|
||||
}
|
||||
else if (type == "warp")
|
||||
{
|
||||
_warpFiles ~= new WarpFile(mmfile);
|
||||
|
||||
stderr.writeln("warp");
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false, path ~ ": not a valid .arc file");
|
||||
}
|
||||
}
|
||||
|
||||
stderr.writeln(".arc files checked");
|
||||
|
||||
throw new Exception("Not implemented yet");
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
module cm3d2.menu;
|
||||
|
||||
import std.bitmanip;
|
||||
import std.conv;
|
||||
import std.math;
|
||||
import std.array;
|
||||
|
@ -8,6 +7,8 @@ import std.range;
|
|||
|
||||
import sdlang;
|
||||
|
||||
import cm3d2;
|
||||
|
||||
class Menu
|
||||
{
|
||||
private static string[string] _translations;
|
||||
|
@ -38,65 +39,23 @@ class Menu
|
|||
{
|
||||
auto menu = new Menu();
|
||||
|
||||
ubyte readByte()
|
||||
{
|
||||
auto value = data[0];
|
||||
data = data[1 .. $];
|
||||
return value;
|
||||
}
|
||||
assert(data.readString() == "CM3D2_MENU", "Not a valid .menu file");
|
||||
|
||||
string readString()
|
||||
{
|
||||
auto length = 0;
|
||||
ubyte[] chars;
|
||||
menu.fileVersion = data.readInt();
|
||||
menu.path = data.readString();
|
||||
menu.name = data.readString();
|
||||
menu.category = data.readString();
|
||||
menu.description = data.readString();
|
||||
|
||||
while (true)
|
||||
{
|
||||
auto lengthByte = readByte();
|
||||
assert(data.readInt() == data.length, "Unexpected data at end of file");
|
||||
|
||||
if (length != 0 && lengthByte < 128)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
length = length << 8 | lengthByte;
|
||||
|
||||
if (lengthByte < 128)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto value = cast(string) data[0 .. length];
|
||||
data = data[length .. $];
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
uint readInt()
|
||||
{
|
||||
auto value = littleEndianToNative!uint(data[0 .. 4]);
|
||||
data = data[4 .. $];
|
||||
return value;
|
||||
}
|
||||
|
||||
assert(readString() == "CM3D2_MENU", "Not a valid .menu file");
|
||||
|
||||
menu.fileVersion = readInt();
|
||||
menu.path = readString();
|
||||
menu.name = readString();
|
||||
menu.category = readString();
|
||||
menu.description = readString();
|
||||
|
||||
assert(readInt() == data.length, "Unexpected data at end of file");
|
||||
|
||||
while(data.length > 0)
|
||||
while (data.length > 0)
|
||||
{
|
||||
string[] line;
|
||||
|
||||
foreach (_; 0 .. readByte())
|
||||
foreach (_; 0 .. data.readByte())
|
||||
{
|
||||
line ~= readString();
|
||||
line ~= data.readString();
|
||||
}
|
||||
|
||||
if (line.length > 0)
|
||||
|
@ -121,7 +80,8 @@ class Menu
|
|||
auto categoryTag = new Tag(null, "category", [Value(category)]);
|
||||
auto descriptionTag = new Tag(null, "description", [Value(description)]);
|
||||
|
||||
auto root = new Tag(null, null, null, null, [versionTag, pathTag, nameTag, categoryTag, descriptionTag]);
|
||||
auto root = new Tag(null, null, null, null, [versionTag, pathTag,
|
||||
nameTag, categoryTag, descriptionTag]);
|
||||
|
||||
Tag[] dataTags;
|
||||
foreach (line; lines)
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
module cm3d2;
|
||||
|
||||
public import cm3d2.util;
|
||||
|
||||
public import cm3d2.menu;
|
||||
public import cm3d2.arc;
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
module cm3d2.util;
|
||||
|
||||
import std.bitmanip;
|
||||
import std.range;
|
||||
import std.traits;
|
||||
|
||||
ubyte readByte(Range)(ref Range data)
|
||||
if (isRandomAccessRange!(Unqual!(Range))
|
||||
&& is(ElementType!Range == ubyte) && hasSlicing!Range)
|
||||
{
|
||||
auto value = data[0];
|
||||
data = data[1 .. $];
|
||||
return value;
|
||||
}
|
||||
|
||||
ubyte[] readBytes(Range)(ref Range data, uint count)
|
||||
if (isRandomAccessRange!(Unqual!(Range))
|
||||
&& is(ElementType!Range == ubyte) && hasSlicing!Range)
|
||||
{
|
||||
ubyte[] bytes = new ubyte[count];
|
||||
|
||||
for (auto i = 0; i < count; i++)
|
||||
{
|
||||
bytes[i] = data.readByte();
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
string readString(Range)(ref Range data)
|
||||
if (isRandomAccessRange!(Unqual!(Range))
|
||||
&& is(ElementType!Range == ubyte) && hasSlicing!Range)
|
||||
{
|
||||
auto length = 0;
|
||||
ubyte[] chars;
|
||||
|
||||
while (true)
|
||||
{
|
||||
auto lengthByte = data.readByte();
|
||||
|
||||
if (length != 0 && lengthByte < 128)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
length = length << 8 | lengthByte;
|
||||
|
||||
if (lengthByte < 128)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto value = cast(string) data[0 .. length];
|
||||
data = data[length .. $];
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
uint readInt(Range)(ref Range data)
|
||||
if (isRandomAccessRange!(Unqual!(Range))
|
||||
&& is(ElementType!Range == ubyte) && hasSlicing!Range)
|
||||
{
|
||||
auto value = littleEndianToNative!uint(data[0 .. 4]);
|
||||
data = data[4 .. $];
|
||||
return value;
|
||||
}
|
Loading…
Reference in New Issue