Add project files.
This commit is contained in:
14
AssetExtractor/AssetExtractor.csproj
Normal file
14
AssetExtractor/AssetExtractor.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
234
AssetExtractor/Lzh.cs
Normal file
234
AssetExtractor/Lzh.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
namespace AssetExtractor;
|
||||
|
||||
/// <summary>
|
||||
/// Decoder for the LZHUF format (LZSS + adaptive Huffman, Okumura/Yoshizaki 1988),
|
||||
/// as written by the Delphi TLZRW1 component in "Good" mode.
|
||||
/// Stream layout: [4B magic "LZH!" = 0x4C5A4821 LE][4B LE original size][LZHUF bitstream].
|
||||
/// </summary>
|
||||
public static class Lzh
|
||||
{
|
||||
public const uint LzhMagic = (((((uint)'L' << 8) + 'Z') << 8) + 'H' << 8) + '!';
|
||||
|
||||
const int N = 4096; // ring buffer size
|
||||
const int F = 60; // lookahead buffer size
|
||||
const int Threshold = 2;
|
||||
const int NChar = 256 - Threshold + F; // 314 character codes
|
||||
const int T = NChar * 2 - 1; // 627, size of freq table
|
||||
const int R = T - 1; // 626, root position
|
||||
const int MaxFreq = 0x8000;
|
||||
|
||||
// Tables for decoding the upper 6 bits of the match position
|
||||
static readonly byte[] DCode = BuildDCode();
|
||||
static readonly byte[] DLen = BuildDLen();
|
||||
|
||||
static byte[] BuildDCode()
|
||||
{
|
||||
// Canonical LZHUF d_code table: value 0 ×32; 1..3 ×16; 4..11 ×8; 12..23 ×4; 24..47 ×2; 48..63 ×1
|
||||
var t = new byte[256];
|
||||
int i = 0, v = 0;
|
||||
foreach (var (values, run) in new[] { (1, 32), (3, 16), (8, 8), (12, 4), (24, 2), (16, 1) })
|
||||
for (int g = 0; g < values; g++, v++)
|
||||
for (int k = 0; k < run; k++)
|
||||
t[i++] = (byte)v;
|
||||
return t;
|
||||
}
|
||||
|
||||
static byte[] BuildDLen()
|
||||
{
|
||||
// Canonical LZHUF d_len table: 3 ×32, 4 ×48, 5 ×64, 6 ×48, 7 ×48, 8 ×16
|
||||
var t = new byte[256];
|
||||
int i = 0;
|
||||
foreach (var (count, val) in new[] { (32, 3), (48, 4), (64, 5), (48, 6), (48, 7), (16, 8) })
|
||||
for (int k = 0; k < count; k++)
|
||||
t[i++] = (byte)val;
|
||||
return t;
|
||||
}
|
||||
|
||||
public static byte[] Decompress(byte[] file)
|
||||
{
|
||||
if (file.Length < 8 || BitConverter.ToUInt32(file, 0) != LzhMagic)
|
||||
throw new InvalidDataException("Not an LZH!-compressed TLZRW1 stream.");
|
||||
int origSize = BitConverter.ToInt32(file, 4);
|
||||
return Decode(file, 8, origSize);
|
||||
}
|
||||
|
||||
static byte[] Decode(byte[] src, int srcOffset, int origSize)
|
||||
{
|
||||
var output = new byte[origSize];
|
||||
int outPos = 0;
|
||||
|
||||
// adaptive Huffman state
|
||||
var freq = new int[T + 1];
|
||||
var prnt = new int[T + NChar];
|
||||
var son = new int[T];
|
||||
|
||||
// init tree
|
||||
for (int i = 0; i < NChar; i++)
|
||||
{
|
||||
freq[i] = 1;
|
||||
son[i] = i + T;
|
||||
prnt[i + T] = i;
|
||||
}
|
||||
for (int i = 0, j = NChar; j <= R; i += 2, j++)
|
||||
{
|
||||
freq[j] = freq[i] + freq[i + 1];
|
||||
son[j] = i;
|
||||
prnt[i] = j;
|
||||
prnt[i + 1] = j;
|
||||
}
|
||||
freq[T] = 0xFFFF;
|
||||
prnt[R] = 0;
|
||||
|
||||
// ring buffer, initialized to spaces
|
||||
var ring = new byte[N + F - 1];
|
||||
Array.Fill(ring, (byte)0x20);
|
||||
int r = N - F;
|
||||
|
||||
// bit reader
|
||||
int srcPos = srcOffset;
|
||||
uint getbuf = 0;
|
||||
int getlen = 0;
|
||||
|
||||
int GetBit()
|
||||
{
|
||||
while (getlen <= 8)
|
||||
{
|
||||
uint b = srcPos < src.Length ? src[srcPos++] : 0u;
|
||||
getbuf |= b << (8 - getlen);
|
||||
getlen += 8;
|
||||
}
|
||||
int result = (int)((getbuf >> 15) & 1);
|
||||
getbuf = (getbuf << 1) & 0xFFFF;
|
||||
getlen--;
|
||||
return result;
|
||||
}
|
||||
|
||||
int GetByte()
|
||||
{
|
||||
while (getlen <= 8)
|
||||
{
|
||||
uint b = srcPos < src.Length ? src[srcPos++] : 0u;
|
||||
getbuf |= b << (8 - getlen);
|
||||
getlen += 8;
|
||||
}
|
||||
int result = (int)((getbuf >> 8) & 0xFF);
|
||||
getbuf = (getbuf << 8) & 0xFFFF;
|
||||
getlen -= 8;
|
||||
return result;
|
||||
}
|
||||
|
||||
void Update(int c)
|
||||
{
|
||||
if (freq[R] == MaxFreq)
|
||||
Reconstruct();
|
||||
|
||||
c = prnt[c + T];
|
||||
do
|
||||
{
|
||||
int k = ++freq[c];
|
||||
// if order is disturbed, swap nodes
|
||||
int l = c + 1;
|
||||
if (k > freq[l])
|
||||
{
|
||||
while (k > freq[l + 1]) l++;
|
||||
freq[c] = freq[l];
|
||||
freq[l] = k;
|
||||
|
||||
int i = son[c];
|
||||
prnt[i] = l;
|
||||
if (i < T) prnt[i + 1] = l;
|
||||
|
||||
int j = son[l];
|
||||
son[l] = i;
|
||||
|
||||
prnt[j] = c;
|
||||
if (j < T) prnt[j + 1] = c;
|
||||
son[c] = j;
|
||||
|
||||
c = l;
|
||||
}
|
||||
c = prnt[c];
|
||||
} while (c != 0);
|
||||
}
|
||||
|
||||
void Reconstruct()
|
||||
{
|
||||
// collect leaf nodes, halve frequencies
|
||||
int j = 0;
|
||||
for (int i = 0; i < T; i++)
|
||||
{
|
||||
if (son[i] >= T)
|
||||
{
|
||||
freq[j] = (freq[i] + 1) / 2;
|
||||
son[j] = son[i];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
// rebuild internal nodes
|
||||
for (int i = 0, k = NChar; k < T; i += 2, k++)
|
||||
{
|
||||
int f = freq[i] + freq[i + 1];
|
||||
int l = k;
|
||||
while (f < freq[l - 1]) l--;
|
||||
Array.Copy(freq, l, freq, l + 1, k - l);
|
||||
freq[l] = f;
|
||||
Array.Copy(son, l, son, l + 1, k - l);
|
||||
son[l] = i;
|
||||
}
|
||||
// reconnect parent pointers
|
||||
for (int i = 0; i < T; i++)
|
||||
{
|
||||
int k = son[i];
|
||||
prnt[k] = i;
|
||||
if (k < T) prnt[k + 1] = i;
|
||||
}
|
||||
}
|
||||
|
||||
int DecodeChar()
|
||||
{
|
||||
int c = son[R];
|
||||
while (c < T)
|
||||
c = son[c + GetBit()];
|
||||
c -= T;
|
||||
Update(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
int DecodePosition()
|
||||
{
|
||||
// upper 6 bits from table
|
||||
int i = GetByte();
|
||||
int c = DCode[i] << 6;
|
||||
int j = DLen[i] - 2;
|
||||
// read lower 6 bits verbatim
|
||||
while (j-- > 0)
|
||||
i = (i << 1) + GetBit();
|
||||
return c | (i & 0x3F);
|
||||
}
|
||||
|
||||
while (outPos < origSize)
|
||||
{
|
||||
int c = DecodeChar();
|
||||
if (c < 256)
|
||||
{
|
||||
output[outPos++] = (byte)c;
|
||||
ring[r] = (byte)c;
|
||||
r = (r + 1) & (N - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
int pos = (r - DecodePosition() - 1) & (N - 1);
|
||||
int len = c - 255 + Threshold;
|
||||
for (int k = 0; k < len && outPos < origSize; k++)
|
||||
{
|
||||
byte b = ring[(pos + k) & (N - 1)];
|
||||
output[outPos++] = b;
|
||||
ring[r] = b;
|
||||
r = (r + 1) & (N - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
360
AssetExtractor/Program.cs
Normal file
360
AssetExtractor/Program.cs
Normal file
@@ -0,0 +1,360 @@
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AssetExtractor;
|
||||
|
||||
/// <summary>
|
||||
/// One-time migration tool: pulls sprites out of the Delphi DFM, converts .omap files
|
||||
/// (TLZRW1 "LZH!" compressed text) to JSON, extracts tileset images from .oil files,
|
||||
/// and copies the sound/music files into the MonoGame Content folder.
|
||||
/// </summary>
|
||||
public static class Program
|
||||
{
|
||||
static string _srcDir = @"C:\Projects\Games\Delphi\Omega Games\Freedom Fighter";
|
||||
static string _outDir = @"C:\Projects\Games\Visual Studio\FreedomFighter\FreedomFighter\Content";
|
||||
|
||||
static readonly string[] Maps = ["intro.omap", "test10.omap", "test7.omap", "test8.omap", "bonus.omap", "lvl5.omap"];
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length > 0) _srcDir = args[0];
|
||||
if (args.Length > 1) _outDir = args[1];
|
||||
|
||||
foreach (var sub in new[] { "gfx", "maps", "tilesets", "sfx", "music" })
|
||||
Directory.CreateDirectory(Path.Combine(_outDir, sub));
|
||||
|
||||
var oilsNeeded = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
ExtractDfmImages(Path.Combine(_srcDir, "GameU.dfm"));
|
||||
|
||||
foreach (var map in Maps)
|
||||
{
|
||||
var oil = ConvertMap(Path.Combine(_srcDir, map));
|
||||
if (oil != null) oilsNeeded.Add(oil);
|
||||
}
|
||||
|
||||
foreach (var oil in oilsNeeded)
|
||||
ExtractOilTileset(Path.Combine(_srcDir, oil));
|
||||
|
||||
CopyAudio();
|
||||
|
||||
Console.WriteLine("Done.");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- DFM images
|
||||
|
||||
record ImageItem(int Index, string Name, string File, int TileWidth, int TileHeight);
|
||||
|
||||
static void ExtractDfmImages(string dfmPath)
|
||||
{
|
||||
var lines = File.ReadAllLines(dfmPath);
|
||||
var manifest = new Dictionary<string, List<ImageItem>>();
|
||||
|
||||
string? currentList = null;
|
||||
string itemName = "";
|
||||
int tileW = 0, tileH = 0;
|
||||
StringBuilder? hex = null;
|
||||
byte[]? pictureData = null;
|
||||
int itemIndex = 0;
|
||||
|
||||
void FlushItem()
|
||||
{
|
||||
if (currentList == null || pictureData == null) return;
|
||||
string safeName = itemName.Length > 0 ? itemName : "unnamed";
|
||||
string file = $"{currentList.ToLowerInvariant()}_{itemIndex:D2}_{safeName}.png";
|
||||
SaveGraphic(pictureData, Path.Combine(_outDir, "gfx", file));
|
||||
manifest.TryAdd(currentList, []);
|
||||
// TileWidth/TileHeight of 0 means "whole image is one frame"
|
||||
manifest[currentList].Add(new ImageItem(itemIndex, itemName, file, tileW, tileH));
|
||||
Console.WriteLine($" {currentList}[{itemIndex}] '{itemName}' -> {file} ({tileW}x{tileH})");
|
||||
itemIndex++;
|
||||
pictureData = null;
|
||||
itemName = "";
|
||||
tileW = tileH = 0;
|
||||
}
|
||||
|
||||
foreach (var raw in lines)
|
||||
{
|
||||
var line = raw.Trim();
|
||||
|
||||
if (hex != null)
|
||||
{
|
||||
bool end = line.EndsWith('}');
|
||||
hex.Append(end ? line[..^1] : line);
|
||||
if (end)
|
||||
{
|
||||
pictureData = Convert.FromHexString(hex.ToString());
|
||||
hex = null;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.StartsWith("object ") && line.Contains(": TOmegaImageList"))
|
||||
{
|
||||
FlushItem();
|
||||
currentList = line["object ".Length..line.IndexOf(':')].Trim();
|
||||
itemIndex = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentList == null) continue;
|
||||
|
||||
if (line.StartsWith("object ")) // next non-imagelist component
|
||||
{
|
||||
FlushItem();
|
||||
currentList = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line == "item")
|
||||
FlushItem();
|
||||
else if (line.StartsWith("Name = '"))
|
||||
itemName = line["Name = '".Length..^1];
|
||||
else if (line.StartsWith("TileWidth = "))
|
||||
tileW = int.Parse(line["TileWidth = ".Length..]);
|
||||
else if (line.StartsWith("TileHeight = "))
|
||||
tileH = int.Parse(line["TileHeight = ".Length..]);
|
||||
else if (line.StartsWith("Picture.Data = {"))
|
||||
hex = new StringBuilder(line["Picture.Data = {".Length..]);
|
||||
}
|
||||
FlushItem();
|
||||
|
||||
var json = JsonSerializer.Serialize(manifest, new JsonSerializerOptions { WriteIndented = true });
|
||||
File.WriteAllText(Path.Combine(_outDir, "gfx", "manifest.json"), json);
|
||||
Console.WriteLine(" wrote gfx/manifest.json");
|
||||
}
|
||||
|
||||
/// <summary>Delphi TPicture blob: [1B class-name length][class name][payload].</summary>
|
||||
static void SaveGraphic(byte[] data, string outPath)
|
||||
{
|
||||
int nameLen = data[0];
|
||||
string cls = Encoding.ASCII.GetString(data, 1, nameLen);
|
||||
int payloadStart = 1 + nameLen;
|
||||
|
||||
if (cls == "TPNGObject")
|
||||
{
|
||||
File.WriteAllBytes(outPath, data[payloadStart..]);
|
||||
}
|
||||
else if (cls == "TBitmap")
|
||||
{
|
||||
// payload: [4B length][BMP bytes]
|
||||
int len = BitConverter.ToInt32(data, payloadStart);
|
||||
var bmpBytes = new ReadOnlySpan<byte>(data, payloadStart + 4, len);
|
||||
SaveBmpAsKeyedPng(bmpBytes.ToArray(), outPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidDataException($"Unknown graphic class '{cls}'");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Convert BMP to PNG, keying out the bottom-left pixel color (Delphi Transparent convention).</summary>
|
||||
static void SaveBmpAsKeyedPng(byte[] bmpBytes, string outPath)
|
||||
{
|
||||
using var ms = new MemoryStream(bmpBytes);
|
||||
using var src = new Bitmap(ms);
|
||||
using var dst = new Bitmap(src.Width, src.Height, PixelFormat.Format32bppArgb);
|
||||
var key = src.GetPixel(0, src.Height - 1);
|
||||
for (int y = 0; y < src.Height; y++)
|
||||
for (int x = 0; x < src.Width; x++)
|
||||
{
|
||||
var c = src.GetPixel(x, y);
|
||||
dst.SetPixel(x, y, c.R == key.R && c.G == key.G && c.B == key.B ? Color.Transparent : c);
|
||||
}
|
||||
dst.Save(outPath, ImageFormat.Png);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- maps
|
||||
|
||||
static string? ConvertMap(string mapPath)
|
||||
{
|
||||
if (!File.Exists(mapPath))
|
||||
{
|
||||
Console.WriteLine($" MISSING map {mapPath}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var text = Encoding.Latin1.GetString(Lzh.Decompress(File.ReadAllBytes(mapPath)));
|
||||
var lines = text.Split("\r\n");
|
||||
int p = 0;
|
||||
string Next() => lines[p++];
|
||||
int NextInt() => int.Parse(Next());
|
||||
|
||||
int w = NextInt();
|
||||
int h = NextInt();
|
||||
string oil = Next();
|
||||
int tileW = NextInt();
|
||||
int tileH = NextInt();
|
||||
|
||||
// Layer1: 17 lines per tile; keep only the pattern index (game uses nothing else)
|
||||
var layer1 = new int[w][];
|
||||
for (int a = 0; a < w; a++)
|
||||
{
|
||||
layer1[a] = new int[h];
|
||||
for (int b = 0; b < h; b++)
|
||||
{
|
||||
p += 8; // x,y,r,g,b,alpha,rotation,image.index
|
||||
layer1[a][b] = NextInt(); // imageindex (pattern)
|
||||
p += 8; // animcount..animlooped
|
||||
}
|
||||
}
|
||||
|
||||
p += w * h * 18; // Layer2 (has extra DoCollision line)
|
||||
p += w * h * 17; // Layer3
|
||||
|
||||
var triggers = new List<object>();
|
||||
for (int a = 0; a < w; a++)
|
||||
for (int b = 0; b < h; b++)
|
||||
{
|
||||
string name = Next();
|
||||
string action = Next();
|
||||
string e1 = Next(), e2 = Next(), e3 = Next();
|
||||
if (action.Length > 0)
|
||||
triggers.Add(new { x = a, y = b, name, action, extra1 = e1, extra2 = e2, extra3 = e3 });
|
||||
}
|
||||
|
||||
var mapObj = new
|
||||
{
|
||||
widthCount = w,
|
||||
heightCount = h,
|
||||
oil = Path.GetFileName(oil),
|
||||
tileWidth = tileW,
|
||||
tileHeight = tileH,
|
||||
layer1,
|
||||
triggers
|
||||
};
|
||||
|
||||
string outFile = Path.Combine(_outDir, "maps", Path.GetFileNameWithoutExtension(mapPath) + ".json");
|
||||
File.WriteAllText(outFile, JsonSerializer.Serialize(mapObj));
|
||||
Console.WriteLine($" {Path.GetFileName(mapPath)}: {w}x{h}, oil='{oil}', {triggers.Count} triggers -> {Path.GetFileName(outFile)}");
|
||||
return oil;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- OIL tilesets
|
||||
|
||||
static readonly byte[] PngSig = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
|
||||
|
||||
/// <summary>
|
||||
/// .oil = binary Delphi component stream. Rather than fully parsing TPF0 we scan for
|
||||
/// embedded graphics: "TPNGObject"+PNG or "TBitmap"+[len]+BMP. Item order is preserved;
|
||||
/// the game's maps only use item 0 (the tileset).
|
||||
/// </summary>
|
||||
static void ExtractOilTileset(string oilPath)
|
||||
{
|
||||
if (!File.Exists(oilPath))
|
||||
{
|
||||
Console.WriteLine($" MISSING oil {oilPath}");
|
||||
return;
|
||||
}
|
||||
|
||||
var data = File.ReadAllBytes(oilPath);
|
||||
string baseName = Path.GetFileNameWithoutExtension(oilPath).ToLowerInvariant();
|
||||
int found = 0;
|
||||
|
||||
for (int i = 0; i < data.Length - 10; i++)
|
||||
{
|
||||
if (data[i] == 0x0A && Matches(data, i + 1, "TPNGObject"))
|
||||
{
|
||||
int start = i + 11;
|
||||
if (start + 8 < data.Length && data.AsSpan(start, 8).SequenceEqual(PngSig))
|
||||
{
|
||||
int end = FindPngEnd(data, start);
|
||||
if (end > 0)
|
||||
{
|
||||
Save(data[start..end], found);
|
||||
found++;
|
||||
i = end - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (data[i] == 0x07 && Matches(data, i + 1, "TBitmap"))
|
||||
{
|
||||
int lenPos = i + 8;
|
||||
if (lenPos + 4 >= data.Length) continue;
|
||||
int len = BitConverter.ToInt32(data, lenPos);
|
||||
int start = lenPos + 4;
|
||||
if (len > 14 && start + len <= data.Length && data[start] == 'B' && data[start + 1] == 'M')
|
||||
{
|
||||
string file = Path.Combine(_outDir, "tilesets", $"{baseName}_{found:D2}.png");
|
||||
SaveBmpAsKeyedPng(data[start..(start + len)], file);
|
||||
Console.WriteLine($" {Path.GetFileName(oilPath)}[{found}] (bmp) -> {Path.GetFileName(file)}");
|
||||
found++;
|
||||
i = start + len - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($" {Path.GetFileName(oilPath)}: {found} images extracted");
|
||||
|
||||
void Save(byte[] png, int idx)
|
||||
{
|
||||
string file = Path.Combine(_outDir, "tilesets", $"{baseName}_{idx:D2}.png");
|
||||
File.WriteAllBytes(file, png);
|
||||
Console.WriteLine($" {Path.GetFileName(oilPath)}[{idx}] (png) -> {Path.GetFileName(file)}");
|
||||
}
|
||||
}
|
||||
|
||||
static bool Matches(byte[] data, int pos, string s)
|
||||
{
|
||||
if (pos + s.Length > data.Length) return false;
|
||||
for (int i = 0; i < s.Length; i++)
|
||||
if (data[pos + i] != (byte)s[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int FindPngEnd(byte[] data, int start)
|
||||
{
|
||||
// walk PNG chunks: [4B len BE][4B type][data][4B crc] until IEND
|
||||
int p = start + 8;
|
||||
while (p + 12 <= data.Length)
|
||||
{
|
||||
int len = (data[p] << 24) | (data[p + 1] << 16) | (data[p + 2] << 8) | data[p + 3];
|
||||
string type = Encoding.ASCII.GetString(data, p + 4, 4);
|
||||
p += 12 + len;
|
||||
if (type == "IEND") return p;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- audio
|
||||
|
||||
static void CopyAudio()
|
||||
{
|
||||
// Files referenced by OmegaMusic1.Songs in GameU.dfm.
|
||||
// 'Level test.mp3' (index 1, level2 music) is missing from the source folder;
|
||||
// Track06.mp3 is copied as a substitute.
|
||||
(string src, string sub)[] files =
|
||||
[
|
||||
(@"Music\white light.mp3", "music"),
|
||||
(@"Music\G2 Final.mp3", "music"),
|
||||
(@"Music\Nazi_Rap.mid", "music"),
|
||||
(@"Music\Main Menu 2.mp3", "music"),
|
||||
(@"Music\G8 Final.mp3", "music"),
|
||||
(@"Music\Thunderization.mp3", "music"),
|
||||
(@"Music\Intro.mp3", "music"),
|
||||
(@"Music\Track06.mp3", "music"),
|
||||
(@"Sounds\fire1.wav", "sfx"),
|
||||
(@"Sounds\reload.wav", "sfx"),
|
||||
(@"Sounds\thump1.wav", "sfx"),
|
||||
(@"Sounds\beep.wav", "sfx"),
|
||||
(@"Sounds\hit4.wav", "sfx"),
|
||||
(@"Sounds\explode2.wav", "sfx"),
|
||||
(@"Sounds\missile.wav", "sfx"),
|
||||
];
|
||||
|
||||
foreach (var (src, sub) in files)
|
||||
{
|
||||
string from = Path.Combine(_srcDir, src);
|
||||
if (!File.Exists(from))
|
||||
{
|
||||
Console.WriteLine($" MISSING audio {src}");
|
||||
continue;
|
||||
}
|
||||
string to = Path.Combine(_outDir, sub, Path.GetFileName(src));
|
||||
File.Copy(from, to, overwrite: true);
|
||||
}
|
||||
Console.WriteLine(" audio copied");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user