Hi everybody, Today I bring you a small project I've been working on (and struggling with).
The idea was to create a opensource dotZip-similar type of extracting and packaging library for the black ops soundfiles.
Sadly I was unable to finish the packaging (replacing) part.
I hope I can get people excited about this and you are free to improve it (please do)
I do not have that much experience involving algorithms and hex'n'bytes'n'stuff so excuse me if I didn't use the right methods (criticism is welcome)
I do believe that the information and the offsets of each flac file is defined in the header, because there's a lot of stuff in the header (that I don't understand, sadly)
Pro's:
Cons:
master131-flac-extractor-style code example:
Code:
Result:
![[Image: EwvWw.png]](http://easycaptu.re/EwvWw.png)
![[Image: GC4Pd.png]](http://easycaptu.re/GC4Pd.png)
All that with 20 lines of code?
Yes, that's the power of SABSFile
Download (library, no source, just example source)
SABSFile.zip (Size: 4.4 KB / Downloads: 184)
Source
The idea was to create a opensource dotZip-similar type of extracting and packaging library for the black ops soundfiles.
Sadly I was unable to finish the packaging (replacing) part.
I hope I can get people excited about this and you are free to improve it (please do)
I do not have that much experience involving algorithms and hex'n'bytes'n'stuff so excuse me if I didn't use the right methods (criticism is welcome)
I do believe that the information and the offsets of each flac file is defined in the header, because there's a lot of stuff in the header (that I don't understand, sadly)
Pro's:
- Scan for FLAC files inside of sabs files
- Get raw bytes from each flac file in there
- Extract file directly from class
- Replace (doesn't work properly, works when extracting again but crashes game.)
- Very low memory usage
Cons:
- Keep in mind that when calling the constructor (new SABSFile("d.sabs")) the class starts looking for offsets which takes a few seconds. Do NOT call it from the main thread in a form application.
- Replacing fails ingame (soundbank failed to initialize)
master131-flac-extractor-style code example:
Code:
Code:
Console.WriteLine("SABSFile library example extractor");
string a = GetSteamPath()+"\\steamapps\\common\\Call of Duty Black Ops II";
foreach(string file in Directory.GetFiles(GetSteamPath()+"\\steamapps\\common\\Call of Duty Black Ops II\\sound", "*.sabs"))
{
Console.WriteLine("Extracting " + Path.GetFileName(file));
SABSFile sabs = new SABSFile(file);
string path = Path.GetDirectoryName(Application.ExecutablePath);
int i = 0;
string dir = path+"\\extracted\\"+Path.GetFileNameWithoutExtension(file);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
else foreach (string filef in Directory.GetFiles(dir)) File.Delete(filef);
foreach (SABSEntry entry in sabs.Entries)
{
i++;
Console.Write("\r Progress: "+Convert.ToInt32((double)i / (double)sabs.Entries.Length * 100.0) + "%");
entry.Extract(dir + "\\" + entry.Offset.ToString("X").ToLower() + ".flac");
}
Console.WriteLine();
}![[Image: EwvWw.png]](http://easycaptu.re/EwvWw.png)
![[Image: GC4Pd.png]](http://easycaptu.re/GC4Pd.png)
All that with 20 lines of code?
Yes, that's the power of SABSFile
Download (library, no source, just example source)
SABSFile.zip (Size: 4.4 KB / Downloads: 184)
Source
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;
namespace JariZ
{
public class SABSFile
{
public SABSFile(string filename) {
if (string.IsNullOrEmpty(filename)) throw new ArgumentException("Invalid argument");
if (Path.GetExtension(filename) != ".sabs") throw new FileLoadException("Only SABS files supported");
if (!File.Exists(filename)) throw new FileNotFoundException("SAB File not found");
/*
try
{*/
_filename = filename;
byte[] header = new byte[8] { 0x66, 0x4C, 0x61, 0x43, 0x00, 0x00, 0x00, 0x22};
HeaderSearch hs = new HeaderSearch(header);
using (FileStream reader = new FileStream(filename, FileMode.Open))
{
int read;
int index = 0x800;
reader.Position = 0x800;
List<int> offsets = new List<int>();
while ((read = reader.ReadByte()) >= 0)
{
index++;
if (hs.Search((byte)read))
{
int offset = index - header.Length;
offsets.Add(offset);
}
}
offsets.Add(Convert.ToInt32(reader.Position-1));
int indexx = 0;
foreach(int offset in offsets)
{
indexx++;
if (indexx != 1)
_entries.Add(new SABSEntry(this, offsets[indexx-2], offset));
}
}
/*
}
catch (Exception z)
{
throw new Exception("Unable to read file", z);
}*/
}
string _filename = null;
public string Filename
{
get
{
return _filename;
}
}
List<SABSEntry> _entries = new List<SABSEntry>();
public SABSEntry[] Entries
{
get
{
return _entries.ToArray();
}
}
}
public class SABSEntry
{
public SABSEntry(SABSFile parent, int offsetstart, int offsetend)
{
_offsetend = offsetend;
_offsetstart = offsetstart;
_parent = parent;
}
public int Offset
{
get
{
return _offsetstart;
}
}
public void Extract(out byte[] buffer)
{
List<byte> lbuffer = new List<byte>();
using (FileStream fs = new FileStream(_parent.Filename, FileMode.Open))
{
fs.Position = _offsetstart;
while (fs.Position <= _offsetend) lbuffer.Add((byte)fs.ReadByte());
}
buffer = lbuffer.ToArray();
}
SABSFile _parent;
public void Extract(string filename)
{
using (FileStream fs = new FileStream(_parent.Filename, FileMode.Open, FileAccess.Read, FileShare.None))
{
if (!Directory.Exists(Path.GetDirectoryName(filename))) Directory.CreateDirectory(Path.GetDirectoryName(filename));
if (!File.Exists(filename)) File.Create(filename).Dispose();
using(FileStream gs = new FileStream(filename, FileMode.Truncate, FileAccess.Write, FileShare.None))
{
fs.Position = _offsetstart;
while (fs.Position <= _offsetend) gs.WriteByte((byte)fs.ReadByte());
gs.Flush();
gs.Close();
fs.Close();
gs.Dispose();
fs.Dispose();
}
}
}
public void Replace(byte[] content)
{
byte[] b = File.ReadAllBytes(_parent.Filename);
List<byte> lb = new List<byte>();
lb.AddRange(b);
lb.RemoveRange(_offsetstart, _offsetend - _offsetstart);
lb.InsertRange(_offsetstart, content);
File.WriteAllBytes(_parent.Filename, lb.ToArray());
/*using (FileStream fs = new FileStream(_parent.Filename, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
fs.Position = _offsetstart;
byte[] otherdata = null;
bool crossed_offset = false;
string tempfile = string.Empty;
foreach(byte bite in content)
{
if (fs.Position <= _offsetend)
{
fs.WriteByte(bite);
}
else
{
//we've crossed our offset, no worries, but we need to start inserting data from now on.
//this is simple. we read the rest of the data, write it to a temporary file (to prevent memleaks)
if (!crossed_offset)
{
int b4 = Convert.ToInt32(fs.Position);
FileStream gs = new FileStream((tempfile = Path.GetRandomFileName()), FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
while (fs.Position < Convert.ToInt32(fs.Length)) gs.WriteByte((byte)fs.ReadByte());
gs.Flush();
gs.Close();
gs.Dispose();
crossed_offset = true;
fs.Position = b4;
}
fs.WriteByte(bite);
}
}
//now that we have written our new file, update the offset
_offsetend = Convert.ToInt32(fs.Position);
if (crossed_offset)
{
fs.Position = _offsetend;
FileStream gs = new FileStream(tempfile, FileMode.Open, FileAccess.Read, FileShare.None);
while (gs.Position < Convert.ToInt32(gs.Length)) fs.WriteByte((byte)gs.ReadByte());
gs.Flush();
gs.Close();
gs.Dispose();
}
fs.Flush();
fs.Close();
fs.Dispose();
}*/
}
public void Replace(string filename)
{
Replace(File.ReadAllBytes(filename));
}
int _offsetstart = 0;
int _offsetend = 0;
}
public class HeaderSearch
{
public HeaderSearch(byte[] header)
{
_bufferlength = header.Length;
_header = header;
buffer = new List<byte>(_bufferlength);
_headerend = _header[_header.Length-1];
//fill buffer
while (buffer.Count != _bufferlength) buffer.Add(0);
}
byte[] _header;
int _bufferlength = 0;
List<byte> buffer;
int _headerend = 0;
public bool Search(byte newbyte)
{
buffer.RemoveAt(0);
buffer.Add(newbyte);
if (newbyte == _headerend)
{
byte[] bbuffer = buffer.ToArray();
if (bbuffer.SequenceEqual(_header))
return true;
else return false;
}
else return false;
}
}
}

![[Image: 30xhrep.png]](http://i48.tinypic.com/30xhrep.png)

Also there is a better method than pattern scanning for the FLAC header/signature. The SABS file actually stores an array of structs containing file size and file position.