Started cleaning out d code. Updated Mangle.
parent
769eae99b3
commit
129da4f214
@ -1 +1 @@
|
||||
Subproject commit fb1eec974c63fa435286456f5c82b9d9387ff900
|
||||
Subproject commit 2a6ed21351464751245c485f933ed788d6cb0698
|
@ -1,370 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (bored.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module bored;
|
||||
|
||||
import std.stdio;
|
||||
import std.cstream;
|
||||
import std.stream;
|
||||
import std.random;
|
||||
import std.file;
|
||||
|
||||
double rnd()
|
||||
{
|
||||
return cast(double)std.random.rand()/uint.max;
|
||||
}
|
||||
|
||||
int rand(int a, int b)
|
||||
{
|
||||
return cast(int)((1+b-a)*rnd()+a);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int gold = 0;
|
||||
int loot = 0;
|
||||
int level = 1;
|
||||
int life = 20;
|
||||
bool play = true;
|
||||
|
||||
writefln("\nWelcome to Norrowind!");
|
||||
|
||||
while(play)
|
||||
{
|
||||
writefln();
|
||||
if(life < 4) writefln("You are badly hurt.");
|
||||
else if(life < 10) writefln("You are injured.");
|
||||
else if(life < 15) writefln("You are slightly wounded.");
|
||||
if(gold) writefln("You have %d gold.", gold);
|
||||
if(loot) writefln("You have loot.");
|
||||
if(level>1) writefln("You are level ", level);
|
||||
writefln("
|
||||
1) Kill a monster
|
||||
2) Read a book
|
||||
3) Read an NPC
|
||||
4) Sell some loot
|
||||
5) Walk around and get drunk on skooma
|
||||
6) Watch TEH k33wL P1XAL SHADID W4T3R!!!!1
|
||||
7) Exit
|
||||
");
|
||||
uint input;
|
||||
dout.writef("Your choice: ");
|
||||
do
|
||||
{
|
||||
input = cast(uint)(din.getc() - '0');
|
||||
}
|
||||
while(input >= 8);
|
||||
|
||||
if(rnd() < 0.01)
|
||||
{
|
||||
writefln("Program has performed an illegal instruction, the programmer will be shot.");
|
||||
break;
|
||||
}
|
||||
|
||||
writefln();
|
||||
|
||||
if(((input == 5) || (input == 3) || (input == 1)) && (loot > 100))
|
||||
{
|
||||
writefln("You are encumbered and cannot move. Try selling some of your junk.");
|
||||
continue;
|
||||
}
|
||||
|
||||
char[] str;
|
||||
int lootinc, goldinc;
|
||||
int oldlife = life;
|
||||
|
||||
switch(input)
|
||||
{
|
||||
case 1: // Hunting
|
||||
if(rnd() < 0.02)
|
||||
{
|
||||
writefln("You killed a Bodak and a Greater Mumm.. oops, wrong game, never mind.");
|
||||
break;
|
||||
}
|
||||
if(rnd() < 0.02)
|
||||
{
|
||||
writefln(
|
||||
"You were killed by an white striped aquatic flying dire gigant dragon
|
||||
bear in a Balmora mansion. This is largely your own fault for using all
|
||||
those plugins.");
|
||||
play=false;
|
||||
break;
|
||||
}
|
||||
if(rnd() < 0.02)
|
||||
{
|
||||
writefln("You were eaten by a grue.");
|
||||
play=false;
|
||||
break;
|
||||
}
|
||||
switch(rand(0,15))
|
||||
{
|
||||
case 0: str = "Fjol the Outlaw"; goldinc = rand(0,70); lootinc = rand(10,120); break;
|
||||
case 1: str = "a Betty Netch"; lootinc = rand(0,7); break;
|
||||
case 2: str = "a Vampire"; goldinc = rand(0,10); lootinc = rand(20,40); break;
|
||||
case 3: str = "a Dremora"; lootinc = rand(50,200); break;
|
||||
case 4: str = "some NPC"; goldinc = rand(0,80); lootinc = rand(3,35); break;
|
||||
case 5: str = "an Ordinator"; lootinc = rand(30,45); break;
|
||||
case 6: str = "a Skeleton"; lootinc = 1; break;
|
||||
case 7: str = "Fargoth"; goldinc = 10; lootinc = 4; break;
|
||||
case 8: str = "a Cliff Racer"; lootinc = 2; break;
|
||||
case 9: str = "Vivec"; lootinc = rand(0,20); goldinc = rand(0,60); life-=rand(1,2); break;
|
||||
case 10: str = "a soultrapped Vivec"; goldinc = rand(0,60); lootinc = rand(100,300);
|
||||
life-=rand(1,3); break;
|
||||
case 11: str = "an Ascended Sleeper"; lootinc = rand(5,12); goldinc = rand(0,10); break;
|
||||
case 12: str = "the entire town of Gnaar Mok"; goldinc = rand(40,50); lootinc = rand(70,140);
|
||||
life-=rand(0,2); break;
|
||||
case 13: str = "a Bethesda programmer for being so late with Oblivion"; break;
|
||||
case 14: str = "a Werewolf. Which is kinda strange since you don't have Bloodmoon"; lootinc = rand(4,50); break;
|
||||
case 15: str = "an important quest character. Way to go"; goldinc = rand(0,40); lootinc = rand(0,70); break;
|
||||
}
|
||||
if(rnd() < 0.65)
|
||||
life -= rand(1,8);
|
||||
if(life > 0)
|
||||
{
|
||||
writefln("You killed ", str, ".");
|
||||
if(life < oldlife) writefln("You were hurt in the fight.");
|
||||
else writefln("You survived the fight unscathed.");
|
||||
if(goldinc) writefln("You got ", goldinc, " bucks.");
|
||||
if(lootinc) writefln("You found some loot.");
|
||||
gold += goldinc;
|
||||
loot += lootinc;
|
||||
if(rnd() < 0.2)
|
||||
{
|
||||
writefln("You have gained a level!");
|
||||
life += rand(3,10);
|
||||
level++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writefln("You met ", str, " and were killed.");
|
||||
play = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:// Book
|
||||
switch(rand(0,5))
|
||||
{
|
||||
case 0:
|
||||
writefln("You read The History of The Emipire and fell asleep.");
|
||||
break;
|
||||
case 1:
|
||||
writefln("You read The Pilgrim's Path and became a fanatical religious nut.");
|
||||
break;
|
||||
case 2:
|
||||
writefln("You read the scroll 'Divine Intervention' and suddenly found yourself
|
||||
outside, wearing only your night gown and slippers.");
|
||||
break;
|
||||
case 3:
|
||||
writefln("You read Divine Metaphysics. Again");
|
||||
if(rnd()<0.09)
|
||||
{
|
||||
writefln("You discovered where the dwarwes went! And, more importantly, where
|
||||
they stashed all their loot.");
|
||||
loot += 1000;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
writefln("You learned a new skill.");
|
||||
if(rnd() < 0.4) level++;
|
||||
break;
|
||||
case 5:
|
||||
writefln("You dropped a book on you toe.");
|
||||
life--;
|
||||
if(life == 0)
|
||||
{
|
||||
writefln("You are dead.");
|
||||
play = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 3://NPC
|
||||
if(rnd()<0.05)
|
||||
{
|
||||
writefln("Nobody wants to speak with you.");
|
||||
break;
|
||||
}
|
||||
writefln("You met an NPC");
|
||||
switch(rand(0,9))
|
||||
{
|
||||
case 0: writefln("He had nothing interesting to say."); break;
|
||||
case 1: writefln("She was really boring."); break;
|
||||
case 2: writefln("You got a quest!"); break;
|
||||
case 3: writefln("You completed a quest and got some dough."); gold += rand(1,10); break;
|
||||
case 4: writefln("The nice NPC gave you a healing potion."); life+=rand(2,4); break;
|
||||
case 5: writefln("You robbed 'em blind and got some loot."); loot+=(10,20); break;
|
||||
case 6: writefln("The guard took some of your money, saying you were
|
||||
late on your child support payments."); gold = gold/3; break;
|
||||
case 7: writefln("You spent some money on bribes"); gold -= gold/4; break;
|
||||
case 8: writefln("You had to travel all the way accross the island to talk to this person."); gold -= gold/4; break;
|
||||
case 9: writefln("The Breton mistook you for his mother, and gave you tons of gold."); gold += 100; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4://Sell
|
||||
if(loot == 0)
|
||||
writefln("You have nothing to sell (except that moon sugar and the home made poetry that nobody wants)");
|
||||
else if(rnd()<0.93)
|
||||
{
|
||||
goldinc = cast(int)(loot*rnd()*2);
|
||||
if(goldinc > loot) writefln("The merchant likes you, you got ", goldinc, " gold for stuff worth only ", loot, ".");
|
||||
if(goldinc <= loot) writefln("The merchant didn't like you, your ", loot, " worth of stuff
|
||||
only got you ", goldinc, " gold.");
|
||||
}
|
||||
else
|
||||
{
|
||||
writefln("You met a talking mudcrab and an unfunny scamp! You got lots of\ncash for your loot.");
|
||||
goldinc = 5*loot;
|
||||
}
|
||||
gold += goldinc;
|
||||
loot = 0;
|
||||
break;
|
||||
case 5://Skooma
|
||||
switch(rand(0,7))
|
||||
{
|
||||
case 0:
|
||||
str = "gigant, flesh eating mushrooms"; break;
|
||||
case 1:
|
||||
str = "a firm, slender and agile female argonian"; break;
|
||||
case 2:
|
||||
str = "dead people and some stupid guy in a golden mask"; break;
|
||||
case 3:
|
||||
str = "the whole world only being part of a computer game"; break;
|
||||
case 4:
|
||||
str = "nothing in particular"; break;
|
||||
case 5:
|
||||
str = "an old, half naked guy giving you orders, insisting you\ncall him 'spymaster'";
|
||||
break;
|
||||
case 6:
|
||||
str = "being a geek who sits in front of a screen all day long"; break;
|
||||
case 7:
|
||||
str = "the clouds, man, the crazy clouds!"; break;
|
||||
}
|
||||
writefln("You fall asleep in a ditch and dream about ", str, ".");
|
||||
break;
|
||||
case 6: //Water effects
|
||||
switch(rand(0,5))
|
||||
{
|
||||
case 0: writefln("Them waves sure are pretty!"); break;
|
||||
case 1:
|
||||
writefln("A slaughter fish jumps up and bites you in the nose.");
|
||||
life--;
|
||||
if(life == 0)
|
||||
{
|
||||
writefln("You are dead.");
|
||||
play = false;
|
||||
}
|
||||
break;
|
||||
case 2: writefln("Those graphics might have looked impressive six years ago...");
|
||||
break;
|
||||
case 3: writefln("You were eaten by a Mudcrab. You are dead."); play=false; break;
|
||||
case 4: writefln("You suddenly realize that the person who made this program has way too much time on his hands.");break;
|
||||
case 5: writefln("You found a note with cheat codes on them."); level+=2; life+=rand(5,15); break;
|
||||
}
|
||||
break;
|
||||
|
||||
// Exit
|
||||
case 7: play=false; break;
|
||||
}
|
||||
}
|
||||
writefln("\nScore:");
|
||||
writefln("Gold: %d : %d points", gold, gold);
|
||||
writefln("Level: %d : %d points", level, (level-1)*40);
|
||||
if(loot) writefln("Loot: you have to sell the loot to get any points for it.");
|
||||
Entry n;
|
||||
|
||||
n.score = gold + (level-1) * 40;
|
||||
|
||||
writefln("Total score: ", n.score);
|
||||
|
||||
Entry[] high = getScores();
|
||||
|
||||
|
||||
|
||||
|
||||
int index = 10;
|
||||
|
||||
foreach(int i, Entry e; high)
|
||||
if(n.score > e.score)
|
||||
{
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
|
||||
writefln();
|
||||
|
||||
if(index != 10)
|
||||
{
|
||||
writef("Congratulations! You've made it to the Hall of Fame.\nEnter your name: ");
|
||||
din.readLine();
|
||||
n.name = din.readLine();
|
||||
|
||||
for(int i = 9; i>index; i--)
|
||||
high[i] = high[i-1];
|
||||
|
||||
high[index] = n;
|
||||
|
||||
setScores(high);
|
||||
}
|
||||
|
||||
writefln("Hall of Fame:");
|
||||
foreach(int i, Entry e; high)
|
||||
if(e.score) writefln("%-2d: %-10d %s", i+1, e.score, e.name);
|
||||
|
||||
writefln("\n(Apologies to Bethesda Softworks)");
|
||||
}
|
||||
|
||||
struct Entry
|
||||
{
|
||||
char[] name;
|
||||
int score;
|
||||
}
|
||||
|
||||
void setScores(Entry[] l)
|
||||
{
|
||||
auto File f = new File("bored.highscores", FileMode.OutNew);
|
||||
foreach(Entry e; l)
|
||||
{
|
||||
f.write(e.name);
|
||||
f.write(e.score);
|
||||
}
|
||||
}
|
||||
|
||||
Entry[] getScores()
|
||||
{
|
||||
Entry[] l;
|
||||
l.length = 10;
|
||||
|
||||
if(exists("bored.highscores"))
|
||||
{
|
||||
auto File f = new File("bored.highscores");
|
||||
foreach(ref Entry e; l)
|
||||
{
|
||||
f.read(e.name);
|
||||
f.read(e.score);
|
||||
}
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
@ -1,366 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (bsafile.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module bsa.bsafile;
|
||||
|
||||
//debug=checkHash;
|
||||
|
||||
// This file does not have any unit tests, since you really need the
|
||||
// data to test it. Use the program named 'bsatool', it uses the NIF
|
||||
// reader library and scans through a bsa archive, providing a good
|
||||
// test of both libraries.
|
||||
|
||||
//import std.stream;
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
import std.mmfile;
|
||||
|
||||
import core.memory;
|
||||
|
||||
import monster.util.aa;
|
||||
|
||||
class BSAFileException : Exception
|
||||
{
|
||||
this(char[] msg) {super("BSAFileException: " ~ msg);}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to read "Bethesda Archive Files", or BSAs.
|
||||
*
|
||||
* The BSA archives are typically held open for the entire lifetime of
|
||||
* the application, and are accessed more or less randomly. For that
|
||||
* reason the BSAFile class uses memory mapped files. However, to be
|
||||
* reasonably memory efficient, only the last requested file is
|
||||
* guaranteed to be mapped at any given time, therefore make sure you
|
||||
* don't use any persistant slices.
|
||||
*
|
||||
*/
|
||||
|
||||
class BSAFile
|
||||
{
|
||||
private:
|
||||
// Size of the blocks to map with the memory mapped file. If set to
|
||||
// 0, we map the entire file, but then we use a LOT of system
|
||||
// memory. There is really no penalty in setting it too small, since
|
||||
// multiple blocks may be mapped simultaneously. However, it MUST be
|
||||
// a multiple of the system page size. TODO: On my system it is 4K,
|
||||
// later on I will have to call getpagesize and the windows
|
||||
// equivalent to find this (include the word "granularity" when you
|
||||
// google for it.) For now I just assume 4K is ok on UNIX, but on
|
||||
// Windows we need 64K. (Hands up if you agree that MMFile should
|
||||
// handle this internally!). UPDATE: This is now duplicated in
|
||||
// util.c_mmfile, if we make it more fancy we should collect it in
|
||||
// one place.
|
||||
version(Windows)
|
||||
static int pageSize = 64*1024;
|
||||
else
|
||||
static int pageSize = 4*1024;
|
||||
|
||||
// Represents one file entry in the archive
|
||||
struct FileStruct
|
||||
{
|
||||
// File size and offset in file. We store the offset from the
|
||||
// beginning of the file, not the offset into the data buffer
|
||||
// (which is what is stored in the archive.)
|
||||
uint fileSize, offset;
|
||||
char[] name;
|
||||
|
||||
void getName(char[] buf, uint start)
|
||||
{
|
||||
if(start >= buf.length)
|
||||
throw new BSAFileException("Name offset outside buffer");
|
||||
|
||||
uint end = start;
|
||||
// We have to search for the end of the name string, marked by a zero.
|
||||
for(; end<buf.length; end++)
|
||||
if(buf[end] == 0) break;
|
||||
|
||||
if(end == buf.length)
|
||||
throw new BSAFileException("String buffer overflow");
|
||||
|
||||
name = buf[start..end];
|
||||
}
|
||||
|
||||
// This currently isn't needed, but it would be if we wanted to
|
||||
// write our own bsa archives.
|
||||
debug(checkHash)
|
||||
{
|
||||
void hashName(out uint hash1, out uint hash2)
|
||||
{
|
||||
uint sum, off, temp, n;
|
||||
|
||||
foreach(char c; name[0..$/2])
|
||||
{
|
||||
sum ^= (cast(uint)c) << (off & 31);
|
||||
off += 8;
|
||||
}
|
||||
hash1 = sum;
|
||||
|
||||
sum = off = 0;
|
||||
|
||||
foreach(char c; name[$/2..$])
|
||||
{
|
||||
temp = (cast(uint)c) << (off & 31);
|
||||
sum ^= temp;
|
||||
n = temp & 0x1F;
|
||||
sum = (sum << (32-n)) | (sum >> n); // "rotate right" operation
|
||||
off += 8;
|
||||
}
|
||||
hash2 = sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MmFile mmf; // Handle to memory mapped file
|
||||
char[] filename; // File name
|
||||
FileStruct files[]; // The file table is stored here
|
||||
bool isLoaded; // Set to true if a file has been loaded
|
||||
|
||||
// An AA for fast file name lookup. The CITextHash is a
|
||||
// case-insensitive text hasher, meaning that all lookups are case
|
||||
// insensitive.
|
||||
HashTable!(char[], int, ESMRegionAlloc, CITextHash) lookup;
|
||||
|
||||
void fail(char[] msg)
|
||||
{
|
||||
throw new BSAFileException(msg ~ "\nFile: " ~ filename);
|
||||
}
|
||||
|
||||
void read()
|
||||
{
|
||||
/*
|
||||
* The layout of a BSA archive is as follows:
|
||||
*
|
||||
* - 12 bytes header, contains 3 ints:
|
||||
* id number - equal to 0x100
|
||||
* dirsize - size of the directory block (see below)
|
||||
* numfiles - number of files
|
||||
*
|
||||
* ---------- start of directory block -----------
|
||||
*
|
||||
* - 8 bytes*numfiles, each record contains
|
||||
* fileSize
|
||||
* offset into data buffer (see below)
|
||||
*
|
||||
* - 4 bytes*numfiles, each record is an offset into the following name buffer
|
||||
*
|
||||
* - name buffer, indexed by the previous table, each string is
|
||||
* null-terminated. Size is (dirsize - 12*numfiles).
|
||||
*
|
||||
* ---------- end of directory block -------------
|
||||
*
|
||||
* - 8*filenum - hast table block, we currently ignore this
|
||||
*
|
||||
* Data buffer:
|
||||
*
|
||||
* - The rest of the archive is file data, indexed by the
|
||||
* offsets in the directory block. The offsets start at 0 at
|
||||
* the beginning of this buffer.
|
||||
*
|
||||
*/
|
||||
assert(!isLoaded);
|
||||
|
||||
ulong fsize = mmf.length;
|
||||
|
||||
if( fsize < 12 )
|
||||
fail("File too small to be a valid BSA archive");
|
||||
|
||||
// Recast the file header as a list of uints
|
||||
uint[] array = cast(uint[]) mmf[0..12];
|
||||
|
||||
if(array[0] != 0x100)
|
||||
fail("Unrecognized BSA header");
|
||||
|
||||
// Total number of bytes used in size/offset-table + filename
|
||||
// sections.
|
||||
uint dirsize = array[1];
|
||||
debug writefln("Directory size: ", dirsize);
|
||||
|
||||
// Number of files
|
||||
uint filenum = array[2];
|
||||
debug writefln("Number of files: ", filenum);
|
||||
|
||||
// Each file must take up at least 21 bytes of data in the
|
||||
// bsa. So if files*21 overflows the file size then we are
|
||||
// guaranteed that the archive is corrupt.
|
||||
if( (filenum*21 > fsize -12) ||
|
||||
(dirsize+8*filenum > fsize -12) )
|
||||
fail("Directory information larger than entire archive");
|
||||
|
||||
// Map the entire directory (skip the hashes if we don't need them)
|
||||
debug(checkHash)
|
||||
void[] mm = mmf[12..(12+dirsize+8*filenum)];
|
||||
else
|
||||
void[] mm = mmf[12..(12+dirsize)];
|
||||
|
||||
// Allocate the file list from esmRegion
|
||||
files = esmRegion.allocateT!(FileStruct)(filenum);
|
||||
|
||||
// Calculate the offset of the data buffer. All file offsets are
|
||||
// relative to this.
|
||||
uint fileDataOffset = 12 + dirsize + 8*filenum;
|
||||
|
||||
// Get a slice of the size/offset table
|
||||
array = cast(uint[])mm[0..(8*filenum)];
|
||||
int index = 0; // Used for indexing array[]
|
||||
|
||||
// Read the size/offset table
|
||||
foreach(ref FileStruct fs; files)
|
||||
{
|
||||
fs.fileSize = array[index++];
|
||||
fs.offset = array[index++] + fileDataOffset;
|
||||
if(fs.offset+fs.fileSize > fsize) fail("Archive contains files outside itself");
|
||||
}
|
||||
|
||||
// Get a slice of the name offset table
|
||||
array = cast(uint[])mm[(8*filenum)..(12*filenum)];
|
||||
|
||||
// Get a slice of the name field
|
||||
char[] nameBuf = cast(char[])mm[(12*filenum)..dirsize];
|
||||
// And copy it!
|
||||
nameBuf = esmRegion.copy(nameBuf);
|
||||
|
||||
// Tell the lookup table how big it should be
|
||||
lookup.rehash(filenum);
|
||||
|
||||
// Loop through the name offsets and pick out the names
|
||||
foreach(int idx, ref FileStruct fs; files)
|
||||
{
|
||||
fs.getName(nameBuf, array[idx]);
|
||||
lookup[fs.name] = idx;
|
||||
debug(2) writefln("%d: %s, %d bytes at %x", idx,
|
||||
fs.name, fs.fileSize, fs.offset);
|
||||
}
|
||||
|
||||
// Code to check if file hashes are correct - this was mostly
|
||||
// used to check our hash algorithm.
|
||||
debug(checkHash)
|
||||
{
|
||||
// Slice the Hash table
|
||||
array = cast(uint[])mm[dirsize..(dirsize+8*filenum)];
|
||||
index = 0;
|
||||
foreach(ref FileStruct fs; files)
|
||||
{
|
||||
uint h1, h2;
|
||||
fs.hashName(h1,h2);
|
||||
uint h11 = array[index++];
|
||||
uint h22 = array[index++];
|
||||
if(h1 != h11) writefln("1 : %d vs. %d", h1, h11);
|
||||
if(h2 != h22) writefln("2 : %d vs. %d", h2, h22);
|
||||
}
|
||||
}
|
||||
|
||||
isLoaded = true;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/* -----------------------------------
|
||||
* BSA management methods
|
||||
* -----------------------------------
|
||||
*/
|
||||
|
||||
this() {}
|
||||
this(char[] file) {open(file);}
|
||||
|
||||
// We should clean up after us
|
||||
~this() {clear();}
|
||||
|
||||
// Open a new file. Clears any existing data
|
||||
void open(char[] file)
|
||||
{
|
||||
clear();
|
||||
filename = file;
|
||||
|
||||
// Open a memory mapped file
|
||||
mmf = new MmFile(
|
||||
file, // File name
|
||||
MmFile.Mode.Read, // Read only
|
||||
0, // We need the entire file
|
||||
null, // Don't care where it is mapped
|
||||
pageSize); // DON'T map the entire file, uses
|
||||
// too much memory.
|
||||
// Load header and file directory
|
||||
read();
|
||||
}
|
||||
|
||||
// Clear all data and close file
|
||||
void clear()
|
||||
{
|
||||
delete mmf;
|
||||
|
||||
lookup.reset;
|
||||
files.length = 0;
|
||||
isLoaded = false;
|
||||
}
|
||||
|
||||
/* -----------------------------------
|
||||
* Archive file routines
|
||||
* -----------------------------------
|
||||
*/
|
||||
|
||||
void[] findSlice(int index)
|
||||
{
|
||||
if(!isLoaded)
|
||||
fail("No archive is open");
|
||||
|
||||
if(index < 0 || index >= files.length)
|
||||
fail("Index out of bounds");
|
||||
|
||||
//writefln("\noffset %d, filesize %d", files[index].offset, files[index].fileSize);
|
||||
// Get a slice of the buffer that comprises this file
|
||||
with(files[index])
|
||||
return mmf[offset..offset+fileSize];
|
||||
}
|
||||
|
||||
int getIndex(char[] name)
|
||||
{
|
||||
int i;
|
||||
|
||||
// Look it up in the AA
|
||||
if( lookup.inList( name, i ) )
|
||||
return i;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Return a slice. This routine converts the name to lower case,
|
||||
// since all BSA file names are stored that way, but references to
|
||||
// them in ESM/ESPs and NIFs are not.
|
||||
void[] findSlice(char[] name)
|
||||
{
|
||||
int i = getIndex(name);
|
||||
if(i == -1) return null;
|
||||
return findSlice(i);
|
||||
}
|
||||
|
||||
// Used by the 'bsatest' program to loop through the entire
|
||||
// archive.
|
||||
FileStruct[] getFiles() { return files; }
|
||||
|
||||
// Number of files in the archive
|
||||
uint numFiles() { return files.length; }
|
||||
|
||||
// Gets the name of the archive file.
|
||||
char[] getName() { return filename; }
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (bsatool.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module bsatool;
|
||||
|
||||
import std.stdio;
|
||||
|
||||
import bsa.bsafile;
|
||||
|
||||
import nif.nif;
|
||||
import nif.niffile;
|
||||
|
||||
import core.memory;
|
||||
|
||||
void scanAllNifs(BSAFile f)
|
||||
{
|
||||
uint totSize;
|
||||
BSAFile.FileStruct[] files = f.getFiles();
|
||||
|
||||
foreach(int ind, BSAFile.FileStruct file; files)
|
||||
{
|
||||
if(file.name[$-4..$] == ".nif" ||
|
||||
file.name[$-3..$] == ".kf" )
|
||||
{
|
||||
void[] str = f.findSlice(ind);
|
||||
totSize += str.length;
|
||||
|
||||
try nifMesh.open(str, file.name);
|
||||
catch(NIFFileException e)
|
||||
writefln("Got exception: %s", e);
|
||||
}
|
||||
}
|
||||
writefln("Total NIF/KF size in this archive: ", totSize);
|
||||
}
|
||||
|
||||
void dumpAllFiles(BSAFile f)
|
||||
{
|
||||
File o = new File();
|
||||
|
||||
auto files = f.getFiles();
|
||||
|
||||
foreach(int ind, file; files)
|
||||
{
|
||||
void[] s = cast(ubyte[]) f.findSlice(ind);
|
||||
char[] name = file.name;
|
||||
assert(s !is null);
|
||||
name = getWinBaseName(name);
|
||||
|
||||
o.open(name, FileMode.OutNew);
|
||||
o.writeExact(s.ptr, s.length);
|
||||
o.close();
|
||||
writefln("Wrote '%s'", name);
|
||||
}
|
||||
}
|
||||
|
||||
void listFiles(BSAFile f)
|
||||
{
|
||||
BSAFile.FileStruct[] files = f.getFiles();
|
||||
|
||||
foreach(BSAFile.FileStruct file; files)
|
||||
writefln(file.name, " (%d bytes)", file.fileSize);
|
||||
}
|
||||
|
||||
int main(char[][] args)
|
||||
{
|
||||
//*
|
||||
int help(char[] msg)
|
||||
{
|
||||
writefln("%s", msg);
|
||||
writefln("Format: bsatool archive.bsa [-x filename] [-l] [-n] [-xa]");
|
||||
writefln(" -x filename extract file");
|
||||
writefln(" -xa extract everything");
|
||||
writefln(" -l list all files");
|
||||
writefln(" -n scan through all nifs");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(args.length < 3) return help("Insufficient arguments");
|
||||
|
||||
initializeMemoryRegions();
|
||||
|
||||
char[] filename, ext;
|
||||
bool list, nifs, extract, dumpAll;
|
||||
foreach(char[] a; args[1..$])
|
||||
if(a == "-l") list = true;
|
||||
else if(a == "-n") nifs = true;
|
||||
else if(a == "-x") extract = true;
|
||||
else if(a == "-xa") dumpAll = true;
|
||||
else if(extract && ext == "") ext = a;
|
||||
else if(filename == "") filename = a;
|
||||
else return help("Unknown option " ~ a);
|
||||
|
||||
if(filename == "") return help("No file specified");
|
||||
|
||||
auto BSAFile f = new BSAFile(filename);
|
||||
|
||||
if(list) listFiles(f);
|
||||
if(nifs) scanAllNifs(f);
|
||||
if(dumpAll) dumpAllFiles(f);
|
||||
if(extract)
|
||||
{
|
||||
if(ext == "") return help("No file to extract");
|
||||
|
||||
void[] s = cast(ubyte[]) f.findSlice(ext);
|
||||
|
||||
if(s.ptr == null)
|
||||
{
|
||||
writefln("File '%s' not found in '%s'", ext, filename);
|
||||
return 1;
|
||||
}
|
||||
ext = getWinBaseName(ext);
|
||||
|
||||
File o = new File(ext, FileMode.OutNew);
|
||||
o.writeExact(s.ptr, s.length);
|
||||
o.close();
|
||||
writefln("File extracted to '%s'", ext);
|
||||
}
|
||||
|
||||
//din.readLine();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Picks out part of a string after the last '\'.
|
||||
char[] getWinBaseName(char[] input)
|
||||
{
|
||||
uint i;
|
||||
for(i = input.length; i > 0; i--)
|
||||
if(input[i-1] == '\\') break;
|
||||
return input[i..$];
|
||||
}
|
||||
|
||||
//import std.cstream;
|
@ -1,51 +0,0 @@
|
||||
###########################
|
||||
# Main program #
|
||||
###########################
|
||||
|
||||
[openmw.d]
|
||||
# Add libraries and the two C++ object files
|
||||
buildflags = -llmygui -llOgreMain -llopenal -llOIS -lluuid -llavcodec -llavformat cpp_ogre.o cpp_avcodec.o cpp_bullet.o bullet/libbulletdynamics.a bullet/libbulletcollision.a bullet/libbulletmath.a
|
||||
|
||||
# Make sure the C++ object files are built first
|
||||
version(Windows) {
|
||||
prebuild = warn Not designed for Windows yet.
|
||||
}
|
||||
version(Posix) {
|
||||
prebuild = make cpp;
|
||||
}
|
||||
# Make sure we recompile the nif files without the debug output
|
||||
prebuild += dsss clean niftool
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# Bsa inspection tool #
|
||||
###########################
|
||||
|
||||
[bsatool.d]
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# Esm inspection tool #
|
||||
###########################
|
||||
|
||||
[esmtool.d]
|
||||
# Because of interdepencies between the ESM code and the resource
|
||||
# manager, we have to include all the C++ stuff.
|
||||
buildflags = -llmygui -llOgreMain -llopenal -llOIS -lluuid -llavcodec -llavformat cpp_ogre.o cpp_avcodec.o cpp_bullet.o bullet/libbulletdynamics.a bullet/libbulletcollision.a bullet/libbulletmath.a
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# Nif inspection tool #
|
||||
###########################
|
||||
|
||||
[niftool.d]
|
||||
buildflags = -debug=warnstd -debug=check -debug=statecheck -debug=strict -debug=verbose
|
||||
# Clean the nif object files to make sure they are recompiled in debug mode
|
||||
prebuild = dsss clean niftool
|
||||
|
||||
[bored.d]
|
||||
|
||||
|
@ -1,63 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (niftool.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module niftool;
|
||||
|
||||
import nif.nif;
|
||||
import nif.niffile;
|
||||
import std.stdio;
|
||||
import std.utf;
|
||||
|
||||
int main(char[][] args)
|
||||
{
|
||||
if(args.length < 2)
|
||||
{
|
||||
writefln("You must specify a file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
initializeMemoryRegions();
|
||||
|
||||
bool errors;
|
||||
|
||||
foreach(char[] fn; args[1..$])
|
||||
{
|
||||
writefln("Opening %s", fn);
|
||||
try nifMesh.open(fn);
|
||||
catch(NIFFileException e)
|
||||
{
|
||||
writefln("Got exception: %s", e);
|
||||
errors = true;
|
||||
}
|
||||
catch(UtfException e)
|
||||
{
|
||||
writefln("Got an UTF-error: %s", e);
|
||||
writefln("We have to ignore the rest of the file at this point. If you want to");
|
||||
writefln("test the entire file, compile the NIF lib without verbose output.");
|
||||
}
|
||||
nifMesh.clear();
|
||||
writefln();
|
||||
}
|
||||
if(errors) return 1;
|
||||
return 0;
|
||||
}
|
@ -1,158 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (cpp_bsaarchive.cpp) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// This file inserts an archive manager for .bsa files into the ogre
|
||||
// resource loading system. It uses callbacks to D to interact with
|
||||
// the D-based bsa handling code. The D bindings are in
|
||||
// core/resource.d.
|
||||
|
||||
// Callbacks to D code
|
||||
|
||||
// Does the file exist in the archives?
|
||||
extern "C" int32_t d_bsaExists(const char *filename);
|
||||
|
||||
// Open a file. Return the pointer and size.
|
||||
extern "C" void d_bsaOpenFile(const char *filename,
|
||||
void **retPtr, uint32_t *retSize);
|
||||
|
||||
|
||||
// This archive does not cover one .bsa file, instead it interacts
|
||||
// with the all the loaded bsas and treates them as one archive.
|
||||
class BSAArchive : public Archive
|
||||
{
|
||||
public:
|
||||
|
||||
BSAArchive(const String& name)
|
||||
: Archive(name, "BSA") {}
|
||||
~BSAArchive() {}
|
||||
|
||||
bool isCaseSensitive(void) const { return false; }
|
||||
|
||||
// The archives are already loaded in D code, and they are never
|
||||
// unloaded. Just ignore these.
|
||||
void load() {}
|
||||
void unload() {}
|
||||
|
||||
// Open a file in the archive. We delegate the function to D-code.
|
||||
DataStreamPtr open(const String& filename) const
|
||||
{
|
||||
//std::cout << "open(" << filename << ")\n";
|
||||
void *ptr;
|
||||
uint32_t size;
|
||||
|
||||
// Open the file. The BSA archives are memory mapped, D code
|
||||
// returns a pointer and a file size.
|
||||
d_bsaOpenFile(filename.c_str(), &ptr, &size);
|
||||
|
||||
return DataStreamPtr(new MemoryDataStream(ptr, size));
|
||||
}
|
||||
|
||||
// This is never called as far as I can see.
|
||||
StringVectorPtr list(bool recursive = true, bool dirs = false)
|
||||
{
|
||||
std::cout << "list(" << recursive << ", " << dirs << ")\n";
|
||||
StringVectorPtr ptr = StringVectorPtr(new StringVector());
|
||||
return ptr;
|
||||
}
|
||||
// Also never called.
|
||||
FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false)
|
||||
{
|
||||
std::cout << "listFileInfo(" << recursive << ", " << dirs << ")\n";
|
||||
FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList());
|
||||
return ptr;
|
||||
}
|
||||
|
||||
time_t getModifiedTime(const String&) { return 0; }
|
||||
|
||||
// After load() is called, find("*") is called once. It doesn't seem
|
||||
// to matter that we return an empty list, exists() gets called on
|
||||
// the correct files anyway.
|
||||
StringVectorPtr find(const String& pattern, bool recursive = true,
|
||||
bool dirs = false)
|
||||
{
|
||||
//std::cout << "find(" << pattern << ", " << recursive
|
||||
// << ", " << dirs << ")\n";
|
||||
StringVectorPtr ptr = StringVectorPtr(new StringVector());
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Gets called once for each of the ogre formats, *.program,
|
||||
// *.material etc. It's also called by MyGUI to find textures, so we
|
||||
// have to channel it through exists().
|
||||
FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true,
|
||||
bool dirs = false)
|
||||
{
|
||||
/*
|
||||
std::cout << "findFileInfo(" << pattern << ", " << recursive
|
||||
<< ", " << dirs << ")\n";
|
||||
*/
|
||||
|
||||
FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList());
|
||||
|
||||
// Check if the file exists (only works for single files - wild
|
||||
// cards and recursive search isn't implemented.)
|
||||
if(exists(pattern))
|
||||
{
|
||||
FileInfo fi;
|
||||
fi.archive = this;
|
||||
fi.filename = pattern;
|
||||
fi.path = "";
|
||||
// It apparently doesn't matter that we return bogus
|
||||
// information
|
||||
fi.compressedSize = fi.uncompressedSize = 0;
|
||||
|
||||
ptr->push_back(fi);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Check if the file exists.
|
||||
bool exists(const String& filename)
|
||||
{
|
||||
//std::cout << "exists(" << filename << ")\n";
|
||||
return d_bsaExists(filename.c_str()) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
// An archive factory for BSA archives
|
||||
class BSAArchiveFactory : public ArchiveFactory
|
||||
{
|
||||
public:
|
||||
virtual ~BSAArchiveFactory() {}
|
||||
|
||||
const String& getType() const
|
||||
{
|
||||
static String name = "BSA";
|
||||
return name;
|
||||
}
|
||||
|
||||
Archive *createInstance( const String& name )
|
||||
{
|
||||
return new BSAArchive(name);
|
||||
}
|
||||
|
||||
void destroyInstance( Archive* arch) { delete arch; }
|
||||
};
|
||||
|
||||
BSAArchiveFactory mBSAFactory;
|
Loading…
Reference in New Issue