Created
July 13, 2012 20:14
-
-
Save rhoot/3107143 to your computer and use it in GitHub Desktop.
gw2DatTools node.js wrapper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2012 rhoot <https://github.com/rhoot> | |
// | |
// This software is provided 'as-is', without any express or implied | |
// warranty. In no event will the authors be held liable for any damages | |
// arising from the use of this software. | |
// | |
// Permission is granted to anyone to use this software for any purpose, | |
// including commercial applications, and to alter it and redistribute it | |
// freely, subject to the following restrictions: | |
// | |
// 1. The origin of this software must not be misrepresented; you must not | |
// claim that you wrote the original software. If you use this software | |
// in a product, an acknowledgment in the product documentation would be | |
// appreciated but is not required. | |
// | |
// 2. Altered source versions must be plainly marked as such, and must not be | |
// misrepresented as being the original software. | |
// | |
// 3. This notice may not be removed or altered from any source | |
// distribution. | |
/* == Includes == */ | |
#include <cassert> | |
#include <cstdint> | |
#include <node.h> | |
#include <node_buffer.h> | |
#include <v8.h> | |
#include <gw2DatTools/compression/inflateDatFileBuffer.h> | |
#include <gw2DatTools/compression/inflateTextureFileBuffer.h> | |
/* == Libraries == */ | |
#pragma comment(lib, "node") | |
#pragma comment(lib, "gw2DatTools") | |
/* == Convenience stuff == */ | |
#define THROW_EXCEPTION(t,s) \ | |
ThrowException(Exception::t(String::New(s))); \ | |
return scope.Close(Undefined()) | |
#define RETURN_DATA(n,b) \ | |
if (returnSize) { \ | |
return scope.Close(Number::New(n)); \ | |
} else { \ | |
return scope.Close(b); \ | |
} | |
using namespace v8; | |
/* == Implementation == */ | |
namespace { | |
struct BufferData | |
{ | |
uint8_t* data; | |
uint32_t length; | |
public: | |
BufferData() : data(nullptr), length(0) {} | |
BufferData& operator=(const BufferData& other) { data = other.data; length = other.length; return *this; } | |
BufferData(Handle<Value> value) : data(nullptr), length(0) | |
{ | |
if (node::Buffer::HasInstance(value)) { | |
auto object = value->ToObject(); | |
data = reinterpret_cast<uint8_t*>(node::Buffer::Data(object)); | |
length = static_cast<uint32_t>(node::Buffer::Length(object)); | |
} | |
} | |
}; | |
uint32_t | |
uncompressedTextureBlockSize(uint32_t width, uint32_t height, uint32_t fourcc) | |
{ | |
uint32_t numBlocks = ((width + 3) >> 2) * ((height + 3) >> 2); | |
switch (fourcc) | |
{ | |
case 0x31545844: // DXT1 | |
case 0x41545844: // DXTA | |
return numBlocks * 8; | |
case 0x32545844: // DXT2 | |
case 0x33545844: // DXT3 | |
case 0x34545844: // DXT4 | |
case 0x35545844: // DXT5 | |
case 0x4c545844: // DXTL | |
case 0x4e545844: // DXTN | |
case 0x58434433: // 3DCX | |
return numBlocks * 16; | |
default: | |
return 0; | |
} | |
} | |
/** | |
* Creates a javascript type Buffer of the given size, as they are different from node::Buffer. | |
* \param[in] size Size of the new buffer. | |
* \return Newly created buffer. | |
*/ | |
Handle<Object> | |
createBuffer(uint32_t size) | |
{ | |
HandleScope scope; | |
// Get Buffer from global scope. | |
Local<Object> global = Context::GetCurrent()->Global(); | |
Local<Value> buffer = global->Get(String::NewSymbol("Buffer")); | |
// Make sure it is a function | |
assert(buffer->IsFunction()); | |
Local<Function> constructor = Local<Function>::Cast(buffer); | |
// Invoke its constructor | |
Local<Value> argv[1] = { Number::New(static_cast<double>(size)) }; | |
Local<Object> instance = constructor->NewInstance(1, argv); | |
return scope.Close(instance); | |
} | |
/** | |
* Wrapper around gw2DatTools' inflateDatFileBuffer function. | |
* \param[in] args Arguments passed from node: | |
* - args[0]: Required input buffer. | |
* - args[1]: Optional output buffer. | |
* \return One of two types: | |
* - Number: If an output buffer was given, the number of bytes written is returned. | |
* - Buffer: If no output buffer was given, a new one is returned containing the data. | |
*/ | |
Handle<Value> | |
inflateDatFileBuffer(const Arguments& args) | |
{ | |
HandleScope scope; | |
// Bail if bad amount of arguments | |
if (args.Length() < 1) { THROW_EXCEPTION(Error, "Too few arguments provided."); } | |
if (args.Length() > 2) { THROW_EXCEPTION(Error, "Too many arguments provided."); } | |
// Argument 0: Required input buffer | |
BufferData inBuffer(args[0]); | |
// This function requires there to be at least 8 bytes in the input buffer, but if there is 8, | |
// there also has to be 9 for gw2DatTools to not crash. | |
if (inBuffer.length < 9) { THROW_EXCEPTION(Error, "Insufficient data in input buffer."); } | |
// Argument 1: Optional output buffer. If given, this function should use it for output and | |
// return the number of bytes written. If not, this function should allocate one of proper | |
// size, and return it. | |
Handle<Value> outBuffer; | |
BufferData outBufferData; | |
bool returnSize; | |
if (args.Length() >= 2) { | |
outBuffer = args[1]; | |
outBufferData = BufferData(outBuffer); | |
returnSize = true; | |
} else { | |
auto uncompressedSize = *reinterpret_cast<uint32_t*>(inBuffer.data + 4); | |
outBuffer = createBuffer(uncompressedSize); | |
outBufferData = BufferData(outBuffer); | |
returnSize = false; | |
} | |
// Early out if the output buffer is 0 bytes | |
if (!outBufferData.length) { RETURN_DATA(0, outBuffer); } | |
// Decompress the dat file | |
try { | |
gw2dt::compression::inflateDatFileBuffer(inBuffer.length, inBuffer.data, outBufferData.length, outBufferData.data); | |
} catch (std::exception e) { | |
THROW_EXCEPTION(Error, e.what()); | |
} | |
// Return different results depending on how this was invoked. | |
RETURN_DATA(outBufferData.length, outBuffer); | |
} | |
/** | |
* Wrapper around gw2DatTools' inflateTextureFileBuffer functions. | |
* \param[in] args Arguments passed from node: | |
* - args[0]: Required input buffer. | |
* - args[1]: Optional output buffer. | |
* \return One of two types: | |
* - Number: If an output buffer was given, the number of bytes written is returned. | |
* - Buffer: If no output buffer was given, a new one is returned containing the data. | |
*/ | |
Handle<Value> | |
inflateTextureFileBuffer(const Arguments& args) | |
{ | |
HandleScope scope; | |
// Bail if bad amount of arguments | |
if (args.Length() < 1) { THROW_EXCEPTION(Error, "Too few arguments provided."); } | |
if (args.Length() > 2) { THROW_EXCEPTION(Error, "Too many arguments provided."); } | |
// Argument 0: Required input buffer | |
BufferData inBuffer(args[0]); | |
// This function requires there to be at least 12 bytes in the input buffer. | |
if (inBuffer.length < 12) { THROW_EXCEPTION(Error, "Insufficient data in input buffer."); } | |
// Argument 1: Optional output buffer. If given, this function should use it for output and | |
// return the number of bytes written. If not, this function should allocate one of proper | |
// size, and return it. | |
Handle<Value> outBuffer; | |
BufferData outBufferData; | |
bool returnSize; | |
if (args.Length() >= 2) { | |
outBuffer = args[1]; | |
outBufferData = BufferData(outBuffer); | |
returnSize = true; | |
} else { | |
auto format = *reinterpret_cast<uint32_t*>(inBuffer.data + 4); | |
auto width = *reinterpret_cast<uint16_t*>(inBuffer.data + 8); | |
auto height = *reinterpret_cast<uint16_t*>(inBuffer.data + 10); | |
auto uncompressedSize = uncompressedTextureBlockSize(width, height, format); | |
if (!uncompressedSize) { THROW_EXCEPTION(TypeError, "Unsupported format type."); } | |
outBuffer = createBuffer(uncompressedSize); | |
outBufferData = BufferData(outBuffer); | |
returnSize = false; | |
} | |
// Early out if the output buffer is 0 bytes | |
if (!outBufferData.length) { RETURN_DATA(0, outBuffer); } | |
// Decompress the first texture block | |
try { | |
gw2dt::compression::inflateTextureFileBuffer(inBuffer.length, inBuffer.data, outBufferData.length, outBufferData.data); | |
} catch (std::exception e) { | |
THROW_EXCEPTION(Error, e.what()); | |
} | |
// Return different results depending on how this was invoked. | |
RETURN_DATA(outBufferData.length, outBuffer); | |
} | |
/** | |
* Wrapper around gw2DatTools' inflateTextureBlockBuffer functions. | |
* \param[in] args Arguments passed from node: | |
* - args[0]: Required block width. | |
* - args[1]: Required block height. | |
* - args[2]: Required texture format. | |
* - args[3]: Required input buffer. | |
* - args[4]: Optional output buffer. | |
* \return One of two types: | |
* - Number: If an output buffer was given, the number of bytes written is returned. | |
* - Buffer: If no output buffer was given, a new one is returned containing the data. | |
*/ | |
Handle<Value> | |
inflateTextureBlockBuffer(const Arguments& args) | |
{ | |
HandleScope scope; | |
// Bail if bad amount of arguments | |
if (args.Length() < 4) { THROW_EXCEPTION(Error, "Too few arguments provided."); } | |
if (args.Length() > 5) { THROW_EXCEPTION(Error, "Too many arguments provided."); } | |
// Argument 0-2: Required resolution and type | |
for (auto i = 0; i <= 2; i++) { | |
if (!args[i]->IsNumber()) { THROW_EXCEPTION(TypeError, "Invalid argument given, number expected."); } | |
if (!args[i]->ToNumber()->IsUint32()) { THROW_EXCEPTION(RangeError, "Invalid number provided."); } | |
} | |
auto width = args[0]->ToNumber()->Uint32Value(); | |
auto height = args[1]->ToNumber()->Uint32Value(); | |
auto format = args[2]->ToNumber()->Uint32Value(); | |
// Argument 3: Required input buffer | |
BufferData inBuffer(args[3]); | |
// Argument 4: Optional output buffer. If given, this function should use it for output and | |
// return the number of bytes written. If not, this function should allocate one of proper | |
// size, and return it. | |
Handle<Value> outBuffer; | |
BufferData outBufferData; | |
bool returnSize; | |
if (args.Length() >= 5) { | |
outBuffer = args[4]; | |
outBufferData = BufferData(outBuffer); | |
returnSize = true; | |
} else { | |
auto uncompressedSize = uncompressedTextureBlockSize(width, height, format); | |
if (!uncompressedSize) { THROW_EXCEPTION(Error, "Unsupported texture format provided."); } | |
outBuffer = createBuffer(uncompressedSize); | |
outBufferData = BufferData(outBuffer); | |
returnSize = false; | |
} | |
// Early out if the output buffer is 0 bytes | |
if (!outBufferData.length) { RETURN_DATA(0, outBuffer); } | |
// Decompress the texture block | |
try { | |
gw2dt::compression::inflateTextureBlockBuffer( | |
static_cast<uint16_t>(width), | |
static_cast<uint16_t>(height), | |
format, | |
inBuffer.length, | |
inBuffer.data, | |
outBufferData.length, | |
outBufferData.data); | |
} catch (std::exception e) { | |
THROW_EXCEPTION(Error, e.what()); | |
} | |
// Return different results depending on how this was invoked. | |
RETURN_DATA(outBufferData.length, outBuffer); | |
} | |
}; // anon namespace | |
/* == Entry point == */ | |
extern "C" NODE_MODULE_EXPORT void | |
init(Handle<Object> target) | |
{ | |
target->Set(String::NewSymbol("inflateDatFile"), FunctionTemplate::New(inflateDatFileBuffer)->GetFunction()); | |
target->Set(String::NewSymbol("inflateTextureFile"), FunctionTemplate::New(inflateTextureFileBuffer)->GetFunction()); | |
target->Set(String::NewSymbol("inflateTextureBlock"), FunctionTemplate::New(inflateTextureBlockBuffer)->GetFunction()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment