Skip to content

Instantly share code, notes, and snippets.

@rhoot
Created July 13, 2012 20:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rhoot/3107143 to your computer and use it in GitHub Desktop.
Save rhoot/3107143 to your computer and use it in GitHub Desktop.
gw2DatTools node.js wrapper
// 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