Initial commit

This commit is contained in:
Villermen 2014-11-17 23:53:03 +01:00
parent efe3fdf580
commit ebd09c117d
8 changed files with 1912 additions and 39 deletions

211
.gitignore vendored
View file

@ -1,43 +1,188 @@
# Windows image file caches ## Ignore Visual Studio temporary files, build results, and
Thumbs.db ## files generated by popular Visual Studio add-ons.
ehthumbs.db
# Folder config file # User-specific files
Desktop.ini *.suo
*.user
*.userosscache
*.sln.docstates
# Recycle Bin used on file shares # User-specific folders
$RECYCLE.BIN/ *.sln.ide/
# Windows Installer files # Build results
*.cab [Dd]ebug/
*.msi [Dd]ebugPublic/
*.msm [Rr]elease/
*.msp [Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
# ========================= # Roslyn cache directories
# Operating System Files *.ide/
# =========================
# OSX # MSTest test Results
# ========================= [Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
.DS_Store #NUNIT
.AppleDouble *.VisualState.xml
.LSOverride TestResult.xml
# Icon must end with two \r # Build Results of an ATL Project
Icon [Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Thumbnails *_i.c
._* *_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Files that might appear on external disk # Chutzpah Test files
.Spotlight-V100 _Chutzpah*
.Trashes
# Directories potentially created on remote AFP share # Visual C++ cache files
.AppleDB ipch/
.AppleDesktop *.aps
Network Trash Folder *.ncb
Temporary Items *.opensdf
.apdisk *.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# If using the old MSBuild-Integrated Package Restore, uncomment this:
#!**/packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/

6
App.config Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>

BIN
ICSharpCode.SharpZipLib.dll Normal file

Binary file not shown.

1175
Options.cs Normal file

File diff suppressed because it is too large Load diff

450
Program.cs Normal file
View file

@ -0,0 +1,450 @@
using System;
using System.IO;
using System.Text;
using System.Linq;
using System.Collections.Generic;
using System.IO.Compression;
using System.Threading;
using System.Diagnostics;
using ICSharpCode.SharpZipLib.BZip2;
using NDesk.Options;
namespace RSCacheTool
{
static class Program
{
static string cacheDir = Environment.ExpandEnvironmentVariables(@"%USERPROFILE%\jagexcache\runescape\LIVE\");
static string outDir = "cache\\";
static int Main(string[] args)
{
bool error = false;
bool help = false, extract = false, combine = false, combineOverwrite = false, combineMergeIncomplete = false;
int extractArchive = -1, combineArchive = 40, combineStartFile = 0;
OptionSet argsParser = new OptionSet() {
{ "h", "show this message", val => { help = true; } },
{ "e", "extract files from cache", val => { extract = true; } },
{ "a=", "single archive to extract, if not given all archives will be extracted", val => { extractArchive = Convert.ToInt32(val); } },
{ "c", "combine sound", val => { combine = true; } },
{ "s=", "archive to combine sounds of, defaults to 40", val => { combineArchive = Convert.ToInt32(val); } },
{ "o", "overwrite existing soundfiles", val => { combineOverwrite = true; } },
{ "i", "merge incomplete files (into special directory)", val => { combineMergeIncomplete = true; } },
};
List<string> otherArgs = argsParser.Parse(args);
for (int i = 0; i < otherArgs.Count; i++)
{
string parsedPath = otherArgs[i];
if (!parsedPath.EndsWith("\\"))
parsedPath += "\\";
parsedPath = Environment.ExpandEnvironmentVariables(parsedPath);
if (Directory.Exists(parsedPath))
{
if (i == 0)
outDir = parsedPath;
else if (i == 1)
cacheDir = parsedPath;
}
else
{
Console.WriteLine("The directory: " + parsedPath + " is not valid.");
error = true;
}
}
if (args.Length == 0 || help)
{
Console.WriteLine(
"Usage: rscachetools [options] outDir cacheDir\n" +
"Provides various tools for extracting and manipulating RuneScape's cache files.\n" +
"\n" +
"Arguments:\n" +
"outDir - The directory in which all files generated by this tool will be placed. Default: cache\\\n" +
"cacheDir - The directory that contains all cache files. Default: %USERPROFILE%\\jagexcache\\runescape\\LIVE\\.\n" +
"\n" +
"Options:"
);
argsParser.WriteOptionDescriptions(Console.Out);
}
else if (!error)
{
if (extract)
ExtractFiles(extractArchive);
if (combine)
CombineSounds(combineArchive, combineStartFile, combineOverwrite, combineMergeIncomplete);
}
return 0;
}
/// <summary>
/// Rips all files from the cachefile and puts them (structured and given a fitting extension where possible) in the fileDir.
/// </summary>
static void ExtractFiles(int archive)
{
int startArchive = 0, endArchive = 255;
if (archive != -1)
{
startArchive = archive;
endArchive = archive;
}
using (FileStream cacheFile = File.Open(cacheDir + "main_file_cache.dat2", FileMode.Open, FileAccess.Read))
{
for (int archiveIndex = startArchive; archiveIndex <= endArchive; archiveIndex++)
{
string indexFileString = cacheDir + "main_file_cache.idx" + archiveIndex.ToString();
if (File.Exists(indexFileString))
{
FileStream indexFile = File.Open(indexFileString, FileMode.Open, FileAccess.Read);
int fileCount = (int)indexFile.Length / 6;
for (int fileIndex = 0; fileIndex < fileCount; fileIndex++)
{
bool fileError = false;
indexFile.Seek(fileIndex * 6, SeekOrigin.Begin);
uint fileSize = indexFile.ReadBytes(3);
long startChunkOffset = indexFile.ReadBytes(3) * 520L;
//Console.WriteLine("New file: archive: {0} file: {1} offset: {3} size: {2}", archiveIndex, fileIndex, fileSize, startChunkOffset);
//filter teeny tiny files (<=1kB) because they gobble up time
if (fileSize > 1024 && startChunkOffset > 0 && startChunkOffset + fileSize <= cacheFile.Length)
{
byte[] buffer = new byte[fileSize];
int writeOffset = 0;
long currentChunkOffset = startChunkOffset;
for (int chunkIndex = 0; writeOffset < fileSize && currentChunkOffset > 0; chunkIndex++)
{
cacheFile.Seek(currentChunkOffset, SeekOrigin.Begin);
int chunkSize;
int checksumFileIndex = 0;
if (fileIndex < 65536)
{
chunkSize = (int)Math.Min(512, fileSize - writeOffset);
}
else
{
//if file index exceeds 2 bytes, add 65536 and read 2(?) extra bytes
chunkSize = (int)Math.Min(510, fileSize - writeOffset);
cacheFile.ReadByte();
checksumFileIndex = (cacheFile.ReadByte() << 16);
}
checksumFileIndex += (int)cacheFile.ReadBytes(2);
int checksumChunkIndex = (int)cacheFile.ReadBytes(2);
long nextChunkOffset = cacheFile.ReadBytes(3) * 520L;
int checksumArchiveIndex = cacheFile.ReadByte();
//Console.WriteLine("Chunk {2}: archive: {3} file: {1} size: {0} nextoffset: {4}", chunkSize, checksumFileIndex, checksumChunkIndex, checksumArchiveIndex, nextChunkOffset);
if (checksumFileIndex == fileIndex && checksumChunkIndex == chunkIndex && checksumArchiveIndex == archiveIndex &&
nextChunkOffset >= 0 && nextChunkOffset < cacheFile.Length)
{
cacheFile.Read(buffer, writeOffset, chunkSize);
writeOffset += chunkSize;
currentChunkOffset = nextChunkOffset;
}
else
{
Console.WriteLine("Ignoring file because a chunk's checksum doesn't match, ideally should not happen.");
fileError = true;
break;
}
}
if (!fileError)
{
//process file
string outFileDir = outDir + archiveIndex + "\\";
string outFileName = fileIndex.ToString();
byte[] tempBuffer;
//decompress gzip
if ((buffer[9] << 8) + buffer[10] == 0x1f8b) //gzip
{
//remove the first 9 bytes cause they seem to be descriptors of sorts (no idea what they do but they are not part of the file)
tempBuffer = new byte[fileSize - 9];
Array.Copy(buffer, 9, tempBuffer, 0, fileSize - 9);
buffer = tempBuffer;
GZipStream decompressionStream = new GZipStream(new MemoryStream(buffer), CompressionMode.Decompress);
int readBytes;
tempBuffer = new byte[0];
do
{
byte[] readBuffer = new byte[100000];
readBytes = decompressionStream.Read(readBuffer, 0, 100000);
int storedBytes = tempBuffer.Length;
Array.Resize(ref tempBuffer, tempBuffer.Length + readBytes);
Array.Copy(readBuffer, 0, tempBuffer, storedBytes, readBytes);
}
while (readBytes == 100000);
buffer = tempBuffer;
Console.WriteLine("File decompressed as gzip.");
}
//decompress bzip2
if (buffer[9] == 0x31 && buffer[10] == 0x41 && buffer[11] == 0x59 && buffer[12] == 0x26 && buffer[13] == 0x53 && buffer[14] == 0x59) //bzip2
{
//remove the first 9 bytes cause they seem to be descriptors of sorts (no idea what they do but they are not part of the file)
tempBuffer = new byte[fileSize - 9];
Array.Copy(buffer, 9, tempBuffer, 0, fileSize - 9);
buffer = tempBuffer;
//prepend file header
byte[] magic = new byte[] {
0x42, 0x5a, //BZ (signature)
0x68, //h (version)
0x31 //*100kB block-size
};
tempBuffer = new byte[magic.Length + buffer.Length];
magic.CopyTo(tempBuffer, 0);
buffer.CopyTo(tempBuffer, magic.Length);
buffer = tempBuffer;
BZip2InputStream decompressionStream = new BZip2InputStream(new MemoryStream(buffer));
int readBytes;
tempBuffer = new byte[0];
do
{
byte[] readBuffer = new byte[100000];
readBytes = decompressionStream.Read(readBuffer, 0, 100000);
int storedBytes = tempBuffer.Length;
Array.Resize(ref tempBuffer, tempBuffer.Length + readBytes);
Array.Copy(readBuffer, 0, tempBuffer, storedBytes, readBytes);
}
while (readBytes == 100000);
buffer = tempBuffer;
Console.WriteLine("File decompressed as bzip2.");
}
//detect ogg: OggS
if ((buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3] == 0x4f676753)
outFileName += ".ogg";
//detect ogg: 5 bytes - OggS
if ((buffer[5] << 24) + (buffer[6] << 16) + (buffer[7] << 8) + buffer[8] == 0x4f676753)
{
tempBuffer = new byte[fileSize - 5];
Array.Copy(buffer, 5, tempBuffer, 0, fileSize - 5);
buffer = tempBuffer;
outFileName += ".ogg";
}
//detect jag: JAGA
if ((buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3] == 0x4a414741)
outFileName += ".jaga";
//detect jag: 5 bytes - JAGA
if ((buffer[5] << 24) + (buffer[6] << 16) + (buffer[7] << 8) + buffer[8] == 0x4a414741)
{
tempBuffer = new byte[fileSize - 5];
Array.Copy(buffer, 5, tempBuffer, 0, fileSize - 5);
buffer = tempBuffer;
outFileName += ".jaga";
}
//detect png: .PNG
if ((uint)(buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3] == 0x89504e47)
outFileName += ".png";
//detect png: 5 bytes - .PNG
if ((uint)(buffer[5] << 24) + (buffer[6] << 16) + (buffer[7] << 8) + buffer[8] == 0x89504e47)
{
tempBuffer = new byte[fileSize - 5];
Array.Copy(buffer, 5, tempBuffer, 0, fileSize - 5);
buffer = tempBuffer;
outFileName += ".png";
}
//create and write file
if (!Directory.Exists(outFileDir))
Directory.CreateDirectory(outFileDir);
using (FileStream outFile = File.Open(outFileDir + outFileName, FileMode.Create, FileAccess.Write))
{
outFile.Write(buffer, 0, buffer.Length);
Console.WriteLine(outFileDir + outFileName);
}
}
}
else
{
Console.WriteLine("Ignoring file because of size or offset.");
}
}
}
}
}
Console.WriteLine("Done extracting files.");
}
/// <summary>
/// Combines the sound files (.jag &amp; .ogg) in the specified archive (40 for the build it was made on), and puts them into the soundtracks directory.
/// </summary>
static void CombineSounds(int archive = 40, int startFile = 0, bool overwriteExisting = false, bool mergeIncomplete = false)
{
string archiveDir = outDir + archive + "\\";
string soundDir = outDir + "sound\\";
//gather all index files
string[] indexFiles = Directory.GetFiles(archiveDir, "*.jag", SearchOption.TopDirectoryOnly);
//create directories
if (!Directory.Exists(soundDir + "incomplete\\"))
Directory.CreateDirectory(soundDir + "incomplete\\");
int i = 0;
foreach (string indexFileString in indexFiles)
{
if (i < startFile)
{
i++;
continue;
}
bool incomplete = false;
List<string> chunkFiles = new List<string>();
using (FileStream indexFileStream = File.Open(indexFileString, FileMode.Open, FileAccess.Read, FileShare.Read))
{
indexFileStream.Seek(32, SeekOrigin.Begin);
while (indexFileStream.ReadBytes(4) != 0x4f676753)
{
uint fileId = indexFileStream.ReadBytes(4);
//check if the file exists and add it to the buffer if it does
if (File.Exists(archiveDir + fileId + ".ogg"))
chunkFiles.Add(archiveDir + fileId + ".ogg");
else
incomplete = true;
}
//copy the first chunk to a temp file so SoX can handle the combining
indexFileStream.Seek(-4, SeekOrigin.Current);
//wait till file is available
while (true)
{
try
{
using (FileStream tempIndexFile = File.Open("~index.ogg", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
{
indexFileStream.CopyTo(tempIndexFile);
break;
}
}
catch (IOException)
{
Thread.Sleep(200);
}
}
}
if (!incomplete || incomplete && mergeIncomplete)
{
string outFile = soundDir + (incomplete ? "incomplete\\" : "") + i + ".ogg";
if (!overwriteExisting && File.Exists(outFile))
Console.WriteLine("Skipping track because it already exists.");
else
{
//combine the files with sox
Console.WriteLine("Running SoX to concatenate ogg audio chunks.");
Process soxProcess = new Process();
soxProcess.StartInfo.FileName = "sox.exe";
soxProcess.StartInfo.Arguments = "--combine concatenate ~index.ogg";
chunkFiles.ForEach((str) =>
{
soxProcess.StartInfo.Arguments += " " + str;
});
soxProcess.StartInfo.Arguments += " " + soundDir + "incomplete\\" + i + ".ogg ";
soxProcess.StartInfo.UseShellExecute = false;
soxProcess.Start();
soxProcess.WaitForExit();
if (soxProcess.ExitCode == 0)
{
if (!incomplete)
{
//clear space
if (File.Exists(soundDir + i + ".ogg"))
File.Delete(soundDir + i + ".ogg");
File.Move(soundDir + "incomplete\\" + i + ".ogg", soundDir + i + ".ogg");
}
Console.WriteLine(soundDir + (incomplete ? "incomplete\\" : "") + i + ".ogg");
}
else
Console.WriteLine("SoX encountered error code " + soxProcess.ExitCode + " and probably didn't finish processing the files.");
}
}
else
Console.WriteLine("Skipping track because it's incomplete.");
i++;
}
//cleanup on isle 4
File.Delete("~index.ogg");
Console.WriteLine("Done combining sound.");
}
/// <summary>
/// Reads a given amount of bytes from the stream.
/// </summary>
public static uint ReadBytes(this Stream stream, byte bytes)
{
if (bytes == 0 || bytes > 4)
throw new ArgumentOutOfRangeException();
uint result = 0;
for (int i = 0; i < bytes; i++)
result += (uint)stream.ReadByte() << (bytes - i - 1) * 8;
return result;
}
}
}

View file

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("RSCacheTool")]
[assembly: AssemblyDescription("Various tools for extracting and manipulating RuneScape's cache files.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Villermen")]
[assembly: AssemblyProduct("RSCacheTool")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("bbc70dd4-1015-4395-8340-4838b6a23815")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

61
RSCacheTool.csproj Normal file
View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{722D3FF9-C3E9-40D9-B68A-3FA1C304A34C}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>RSCacheTool</RootNamespace>
<AssemblyName>RSCacheTool</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="ICSharpCode.SharpZipLib">
<HintPath>.\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Options.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Content Include="sox.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

BIN
sox.exe Normal file

Binary file not shown.