Skip to content

Commit

Permalink
Added basic support for exporting drivers/karts/courses from Mario Ka…
Browse files Browse the repository at this point in the history
…rt: Double Dash. Some characters (Toad/Toadette) and some notable models are still missing.
  • Loading branch information
MeltyPlayer committed Oct 19, 2021
1 parent ea291a2 commit 61d780a
Show file tree
Hide file tree
Showing 8 changed files with 339 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
"Luigi's Mansion": {
"commandName": "Project",
"commandLineArgs": "luigis_mansion"
},
"Paper Mario: The Thousand Year Door": {
"commandName": "Project",
"commandLineArgs": "paper_mario_the_thousand_year_door"
},
"Mario Kart: Double Dash": {
"commandName": "Project",
"commandLineArgs": "mario_kart_double_dash"
}
}
}
10 changes: 10 additions & 0 deletions FinModelUtility/UniversalModelExtractor/src/cli/Cli.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

using uni.games.animal_crossing;
using uni.games.luigis_mansion;
using uni.games.mario_kart_double_dash;
using uni.games.ocarina_of_time_3d;
using uni.games.paper_mario_the_thousand_year_door;
using uni.games.pikmin_1;
using uni.games.pikmin_2;
using uni.games.super_mario_sunshine;
Expand All @@ -21,7 +23,9 @@ public static int Main(string[] args) {
args,
typeof(AnimalCrossingOptions),
typeof(LuigisMansionOptions),
typeof(MarioKartDoubleDashOptions),
typeof(OcarinaOfTime3dOptions),
typeof(PaperMarioTheThousandYearDoorOptions),
typeof(Pikmin1Options),
typeof(Pikmin2Options),
typeof(SuperMarioSunshineOptions),
Expand All @@ -32,9 +36,15 @@ public static int Main(string[] args) {
.WithParsed((LuigisMansionOptions automaticOpts) => {
new LuigisMansionExtractor().ExtractAll();
})
.WithParsed((MarioKartDoubleDashOptions automaticOpts) => {
new MarioKartDoubleDashExtractor().ExtractAll();
})
.WithParsed((OcarinaOfTime3dOptions automaticOpts) => {
new OcarinaOfTime3dExtractor().ExtractAll();
})
.WithParsed((PaperMarioTheThousandYearDoorOptions automaticOpts) => {
new PaperMarioTheThousandYearDoorExtractor().ExtractAll();
})
.WithParsed((Pikmin1Options automaticOpts) => {
new Pikmin1Extractor().ExtractAll();
})
Expand Down
6 changes: 6 additions & 0 deletions FinModelUtility/UniversalModelExtractor/src/cli/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ public class AnimalCrossingOptions {}
[Verb("luigis_mansion", HelpText = "Extract models from Luigi's Mansion.")]
public class LuigisMansionOptions {}

[Verb("mario_kart_double_dash", HelpText = "Extract models from Mario Kart: Double Dash.")]
public class MarioKartDoubleDashOptions { }

[Verb("ocarina_of_time_3d",
HelpText = "Extract models from Ocarina of Time 3d.")]
public class OcarinaOfTime3dOptions {}

[Verb("paper_mario_the_thousand_year_door", HelpText = "Extract models from Paper Mario: The Thousand Year Door.")]
public class PaperMarioTheThousandYearDoorOptions { }

[Verb("pikmin_1", HelpText = "Extract models from Pikmin 1.")]
public class Pikmin1Options {}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
using System;
using System.Collections.Generic;
using System.Linq;

using bmd.api;

using fin.io;
using fin.log;
using fin.util.asserts;

using uni.msg;
using uni.platforms;
using uni.platforms.gcn;
using uni.util.io;

namespace uni.games.mario_kart_double_dash {
public class MarioKartDoubleDashExtractor {
private readonly ILogger logger_ =
Logging.Create<MarioKartDoubleDashExtractor>();

public void ExtractAll() {
var marioKartDoubleDashExtractorRom =
DirectoryConstants.ROMS_DIRECTORY.TryToGetFile(
"mario_kart_double_dash.gcm");

var options = GcnFileHierarchyExtractor.Options.Standard()
.UseRarcDumpForExtensions(".arc");
var fileHierarchy =
new GcnFileHierarchyExtractor()
.ExtractFromRom(options, marioKartDoubleDashExtractorRom);

this.ExtractDrivers_(fileHierarchy);
this.ExtractKarts_(fileHierarchy);
this.ExtractCourses_(fileHierarchy);
// TODO: Extract "enemies"
// TODO: Extract "objects"
}

private void ExtractKarts_(IFileHierarchy fileHierarchy) {
var kartSubdir = fileHierarchy.Root.TryToGetSubdir(@"MRAM\kart");

foreach (var subdir in kartSubdir.Subdirs) {
var bmdFiles = subdir.FilesWithExtension(".bmd")
.ToArray();
this.ExtractModels_(subdir, bmdFiles);
}
}

private void ExtractDrivers_(IFileHierarchy fileHierarchy) {
var mramSubdir = fileHierarchy.Root.TryToGetSubdir(@"MRAM\driver");

{
var plumberNames = new[] {"mario", "luigi",};
var plumberSubdirs =
mramSubdir.Subdirs.Where(
subdir => plumberNames.Contains(subdir.Name));
var plumberCommon = mramSubdir.TryToGetSubdir("cmn_hige");
foreach (var plumberSubdir in plumberSubdirs) {
this.ExtractFromSeparateDriverDirectories_(
plumberSubdir,
plumberCommon);
}
}

{
var babyNames = new[] {"babymario", "babyluigi"};
var babySubdirs =
mramSubdir.Subdirs.Where(
subdir => babyNames.Contains(subdir.Name));
var babyCommon = mramSubdir.TryToGetSubdir("cmn_baby");
foreach (var babySubdir in babySubdirs) {
this.ExtractFromSeparateDriverDirectories_(
babySubdir,
babyCommon);
}
}

{
var princessNames = new[] {"daisy", "peach"};
var princessSubdirs =
mramSubdir.Subdirs.Where(
subdir => princessNames.Contains(subdir.Name));
var princessCommon = mramSubdir.TryToGetSubdir("cmn_hime");
foreach (var princessSubdir in princessSubdirs) {
this.ExtractFromSeparateDriverDirectories_(
princessSubdir,
princessCommon);
}
}

{
var lizardNames = new[] {"catherine", "yoshi"};
var lizardSubdirs =
mramSubdir.Subdirs.Where(
subdir => lizardNames.Contains(subdir.Name));
var lizardCommon = mramSubdir.TryToGetSubdir("cmn_liz");
foreach (var lizardSubdir in lizardSubdirs) {
this.ExtractFromSeparateDriverDirectories_(
lizardSubdir,
lizardCommon);
}
}

// TODO: Where are toad's animations?

{
var koopaNames = new[] {"patapata", "nokonoko"};
var koopaSubdirs =
mramSubdir.Subdirs.Where(
subdir => koopaNames.Contains(subdir.Name));
var koopaCommon = mramSubdir.TryToGetSubdir("cmn_zako");
foreach (var koopaSubdir in koopaSubdirs) {
this.ExtractFromSeparateDriverDirectories_(
koopaSubdir,
koopaCommon);
}
}

{
var standaloneNames = new[] {
"bosspakkun",
"dk",
"dkjr",
"kingteresa",
"koopa",
"koopajr",
"waluigi",
"wario",
};
var standaloneSubdirs =
mramSubdir.Subdirs.Where(
subdir => standaloneNames.Contains(subdir.Name));
foreach (var standaloneSubdir in standaloneSubdirs) {
this.ExtractFromDriverDirectory_(standaloneSubdir);
}
}
}

private void ExtractFromDriverDirectory_(
IFileHierarchyDirectory directory) {
var bmdFiles = directory.FilesWithExtension(".bmd")
.ToArray();
var bcxFiles = directory.FilesWithExtensions(".bca", ".bck")
.Select(file => file.Impl)
.ToArray();

var driverBmdFiles = bmdFiles
.Where(file => file.Name.StartsWith("driver"))
.ToArray();
var driverBcxFiles =
bcxFiles.Where(file => file.Name.StartsWith("b_") ||
file.Name.StartsWith("c_") ||
file.Name.StartsWith("all"))
.ToArray();
this.ExtractModels_(directory,
driverBmdFiles,
driverBcxFiles,
null,
true);

var otherBmdFiles = bmdFiles.Where(file => !driverBmdFiles.Contains(file))
.ToArray();
if (otherBmdFiles.Length > 0) {
var otherBcxFiles =
bcxFiles.Where(file => !driverBcxFiles.Contains(file))
.ToArray();
this.ExtractModels_(directory,
otherBmdFiles,
otherBcxFiles,
null,
true);
}
}

private void ExtractFromSeparateDriverDirectories_(
IFileHierarchyDirectory directory,
IFileHierarchyDirectory common) {
Asserts.Nonnull(common);

var bmdFiles = directory.FilesWithExtension(".bmd")
.ToArray();
var commonBcxFiles = common.FilesWithExtensions(".bca", ".bck")
.Select(file => file.Impl)
.ToArray();
var localBcxFiles = directory.FilesWithExtensions(".bca", ".bck")
.Select(file => file.Impl)
.ToArray();

this.ExtractModels_(directory,
bmdFiles,
commonBcxFiles.Concat(localBcxFiles).ToArray(),
null,
true);
}

private void ExtractCourses_(IFileHierarchy fileHierarchy) {
var courseSubdir = fileHierarchy.Root.TryToGetSubdir("Course");

foreach (var subdir in courseSubdir.Subdirs) {
var bmdFiles = subdir.FilesWithExtension(".bmd")
.ToArray();
if (bmdFiles.Length == 0) {
continue;
}

var btiFiles = subdir.FilesWithExtension(".bti")
.Select(file => file.Impl)
.ToArray();
this.ExtractModels_(subdir,
bmdFiles,
null,
btiFiles,
true);

// TODO: Extract objects
}
}

private void ExtractModels_(
IFileHierarchyDirectory directory,
IReadOnlyList<IFileHierarchyFile> bmdFiles,
IReadOnlyList<IFile>? bcxFiles = null,
IReadOnlyList<IFile>? btiFiles = null,
bool allowMultipleAnimatedModels = false
) {
Asserts.True(bmdFiles.Count > 0);

var outputDirectory =
GameFileHierarchyUtil.GetOutputDirectoryForDirectory(directory);

var matches = 0;
var existingModelFiles =
outputDirectory.GetExistingFiles()
.Where(file => file.Extension == ".fbx" ||
file.Extension == ".glb")
.ToArray();

foreach (var bmdFile in bmdFiles) {
if (existingModelFiles.Any(
existingModelFile => {
var existingName = existingModelFile.Name.Substring(
0,
existingModelFile.Name.Length -
existingModelFile.Extension.Length);
var bmdName =
bmdFile.Name.Substring(0,
bmdFile.Name.Length - ".mod".Length);

return bmdName == existingName ||
bmdName + "_gltf" == existingName;
})) {
++matches;
}
}

if (matches == bmdFiles.Count) {
this.logger_.LogInformation(
$"Model(s) already processed from {directory.LocalPath}");
return;
}

bcxFiles ??= new List<IFile>();
btiFiles ??= new List<IFile>();

if (bmdFiles.Count == 1) {
MessageUtil.LogExtracting(this.logger_, bmdFiles[0]);
} else {
MessageUtil.LogExtracting(this.logger_, directory, bmdFiles);
}

try {
new ManualBmd2FbxApi().Process(outputDirectory,
bmdFiles.Select(file => file.FullName)
.ToArray(),
bcxFiles.Select(file => file.FullName)
.ToArray(),
btiFiles.Select(file => file.FullName)
.ToArray(),
!allowMultipleAnimatedModels,
60);
} catch (Exception e) {
this.logger_.LogError(e.ToString());
}
this.logger_.LogInformation(" ");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using bmd.api;

using fin.log;

using uni.platforms;
using uni.platforms.gcn;

namespace uni.games.paper_mario_the_thousand_year_door {
public class PaperMarioTheThousandYearDoorExtractor {
private readonly ILogger logger_ =
Logging.Create<PaperMarioTheThousandYearDoorExtractor>();

public void ExtractAll() {
var paperMarioTheThousandYearDoorRom =
DirectoryConstants.ROMS_DIRECTORY.TryToGetFile(
"paper_mario_the_thousand_year_door.gcm");

var options = GcnFileHierarchyExtractor.Options.Standard();
var fileHierarchy =
new GcnFileHierarchyExtractor()
.ExtractFromRom(options, paperMarioTheThousandYearDoorRom);
}
}
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Command-line tools for extracting models from games en-masse. Separate batch scr

## Supported games

- Mario Kart: Double Dash (`mario_kart_double_dash`)
- Pikmin 1 (`pikmin_1.gcm`)
- Pikmin 2 (`pikmin_2.gcm`)
- Super Mario Sunshine (`super_mario_sunshine.gcm`)
Expand Down
3 changes: 3 additions & 0 deletions cli/rip_mario_kart_double_dash.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
tools\universal_model_extractor\universal_model_extractor.exe mario_kart_double_dash

pause
Binary file not shown.

0 comments on commit 61d780a

Please sign in to comment.