mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-11-23 11:35:19 +01:00
7c0aa811ec
Signed-off-by: Sergey Isakov <isakov-sl@bk.ru>
681 lines
14 KiB
C
681 lines
14 KiB
C
/*
|
|
* aml_generator.c
|
|
* Chameleon
|
|
*
|
|
* Created by Mozodojo on 20/07/10.
|
|
* Copyright 2010 mozo. All rights reserved.
|
|
*
|
|
* additions and corrections by Slice and pcj, 2012.
|
|
*/
|
|
|
|
#include "AmlGenerator.h"
|
|
|
|
BOOLEAN aml_add_to_parent(AML_CHUNK* parent, AML_CHUNK* node)
|
|
{
|
|
if (parent && node)
|
|
{
|
|
switch (parent->Type)
|
|
{
|
|
case AML_CHUNK_NONE:
|
|
case AML_CHUNK_BYTE:
|
|
case AML_CHUNK_WORD:
|
|
case AML_CHUNK_DWORD:
|
|
case AML_CHUNK_QWORD:
|
|
case AML_CHUNK_ALIAS:
|
|
MsgLog("aml_add_to_parent: node doesn't support child nodes!\n");
|
|
return FALSE;
|
|
case AML_CHUNK_NAME:
|
|
if (parent->First)
|
|
{
|
|
MsgLog("aml_add_to_parent: name node supports only one child node!\n");
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!parent->First)
|
|
parent->First = node;
|
|
|
|
if (parent->Last)
|
|
parent->Last->Next = node;
|
|
|
|
parent->Last = node;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
AML_CHUNK* aml_create_node(AML_CHUNK* parent)
|
|
{
|
|
AML_CHUNK* node = (AML_CHUNK*)AllocateZeroPool(sizeof(AML_CHUNK));
|
|
|
|
aml_add_to_parent(parent, node);
|
|
|
|
return node;
|
|
}
|
|
|
|
void aml_destroy_node(AML_CHUNK* node)
|
|
{
|
|
// Delete child nodes
|
|
AML_CHUNK* child = node->First;
|
|
|
|
while (child)
|
|
{
|
|
AML_CHUNK* next = child->Next;
|
|
|
|
if (child->Buffer)
|
|
FreePool(child->Buffer);
|
|
|
|
FreePool(child);
|
|
|
|
child = next;
|
|
}
|
|
|
|
// Free node
|
|
if (node->Buffer)
|
|
FreePool(node->Buffer);
|
|
|
|
FreePool(node);
|
|
}
|
|
|
|
AML_CHUNK* aml_add_buffer(AML_CHUNK* parent, /* CONST*/ CHAR8* buffer, UINT32 size)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
node->Type = AML_CHUNK_NONE;
|
|
node->Length = (UINT16)size;
|
|
node->Buffer = AllocateZeroPool (node->Length);
|
|
CopyMem(node->Buffer, buffer, node->Length);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_byte(AML_CHUNK* parent, UINT8 value)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
node->Type = AML_CHUNK_BYTE;
|
|
|
|
node->Length = 1;
|
|
node->Buffer = AllocateZeroPool (node->Length);
|
|
node->Buffer[0] = value;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_word(AML_CHUNK* parent, UINT16 value)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
node->Type = AML_CHUNK_WORD;
|
|
node->Length = 2;
|
|
node->Buffer = AllocateZeroPool (node->Length);
|
|
node->Buffer[0] = value & 0xff;
|
|
node->Buffer[1] = value >> 8;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_dword(AML_CHUNK* parent, UINT32 value)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
node->Type = AML_CHUNK_DWORD;
|
|
node->Length = 4;
|
|
node->Buffer = AllocateZeroPool (node->Length);
|
|
node->Buffer[0] = value & 0xff;
|
|
node->Buffer[1] = (value >> 8) & 0xff;
|
|
node->Buffer[2] = (value >> 16) & 0xff;
|
|
node->Buffer[3] = (value >> 24) & 0xff;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_qword(AML_CHUNK* parent, UINT64 value)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
node->Type = AML_CHUNK_QWORD;
|
|
node->Length = 8;
|
|
node->Buffer = AllocateZeroPool (node->Length);
|
|
node->Buffer[0] = value & 0xff;
|
|
node->Buffer[1] = RShiftU64(value, 8) & 0xff;
|
|
node->Buffer[2] = RShiftU64(value, 16) & 0xff;
|
|
node->Buffer[3] = RShiftU64(value, 24) & 0xff;
|
|
node->Buffer[4] = RShiftU64(value, 32) & 0xff;
|
|
node->Buffer[5] = RShiftU64(value, 40) & 0xff;
|
|
node->Buffer[6] = RShiftU64(value, 48) & 0xff;
|
|
node->Buffer[7] = RShiftU64(value, 56) & 0xff;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
UINT32 aml_fill_simple_name(CHAR8* buffer, /* CONST*/ CHAR8* name)
|
|
{
|
|
if (AsciiStrLen(name) < 4)
|
|
{
|
|
// MsgLog("aml_fill_simple_name: simple name %a has incorrect lengh! Must be 4.\n", name);
|
|
return 0;
|
|
}
|
|
|
|
CopyMem(buffer, name, 4);
|
|
return 4;
|
|
}
|
|
|
|
UINT32 aml_fill_name(AML_CHUNK* node, /* CONST*/ CHAR8* name)
|
|
{
|
|
INTN len, offset, count;
|
|
UINT32 root = 0;
|
|
|
|
if (!node)
|
|
return 0;
|
|
|
|
len = AsciiStrLen(name);
|
|
offset = 0;
|
|
count = len >> 2;
|
|
|
|
if ((len % 4) > 1 || count == 0)
|
|
{
|
|
// MsgLog("aml_fill_name: pathname %a has incorrect length! Must be 4, 8, 12, 16, etc...\n", name);
|
|
return 0;
|
|
}
|
|
|
|
if (((len % 4) == 1) && (name[0] == '\\'))
|
|
root++;
|
|
|
|
if (count == 1)
|
|
{
|
|
node->Length = (UINT16)(4 + root);
|
|
node->Buffer = AllocateZeroPool (node->Length+4);
|
|
CopyMem(node->Buffer, name, 4 + root);
|
|
offset += 4 + root;
|
|
return (UINT32)offset;
|
|
}
|
|
|
|
if (count == 2)
|
|
{
|
|
node->Length = 2 + 8;
|
|
node->Buffer = AllocateZeroPool (node->Length+4);
|
|
node->Buffer[offset++] = 0x5c; // Root Char
|
|
node->Buffer[offset++] = 0x2e; // Double name
|
|
CopyMem(node->Buffer+offset, name + root, 8);
|
|
offset += 8;
|
|
return (UINT32)offset;
|
|
}
|
|
|
|
node->Length = (UINT16)(3 + (count << 2));
|
|
node->Buffer = AllocateZeroPool (node->Length+4);
|
|
node->Buffer[offset++] = 0x5c; // Root Char
|
|
node->Buffer[offset++] = 0x2f; // Multi name
|
|
node->Buffer[offset++] = (CHAR8)count; // Names count
|
|
CopyMem(node->Buffer+offset, name + root, count*4);
|
|
offset += count*4;
|
|
return (UINT32)offset; //node->Length;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_scope(AML_CHUNK* parent, /* CONST*/ CHAR8* name)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
node->Type = AML_CHUNK_SCOPE;
|
|
|
|
aml_fill_name(node, name);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_name(AML_CHUNK* parent, /* CONST*/ CHAR8* name)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
node->Type = AML_CHUNK_NAME;
|
|
|
|
aml_fill_name(node, name);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_method(AML_CHUNK* parent, /* CONST*/ CHAR8* name, UINT8 args)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
UINTN offset = aml_fill_name(node, name);
|
|
node->Type = AML_CHUNK_METHOD;
|
|
node->Length++;
|
|
node->Buffer[offset] = args;
|
|
// AML_CHUNK* meth = aml_add_byte(node, args);
|
|
// return meth;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
AML_CHUNK* aml_add_package(AML_CHUNK* parent)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
node->Type = AML_CHUNK_PACKAGE;
|
|
|
|
node->Length = 1;
|
|
node->Buffer = AllocateZeroPool (node->Length);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_alias(AML_CHUNK* parent, /* CONST*/ CHAR8* name1, /* CONST*/ CHAR8* name2)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
node->Type = AML_CHUNK_ALIAS;
|
|
|
|
node->Length = 8;
|
|
node->Buffer = AllocateZeroPool (node->Length);
|
|
aml_fill_simple_name(node->Buffer, name1);
|
|
aml_fill_simple_name(node->Buffer+4, name2);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_return_name(AML_CHUNK* parent, /* CONST*/ CHAR8* name)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
node->Type = AML_CHUNK_RETURN;
|
|
aml_fill_name(node, name);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_return_byte(AML_CHUNK* parent, UINT8 value)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
node->Type = AML_CHUNK_RETURN;
|
|
aml_add_byte(node, value);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_device(AML_CHUNK* parent, /* CONST*/ CHAR8* name)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
node->Type = AML_CHUNK_DEVICE;
|
|
aml_fill_name(node, name);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_local0(AML_CHUNK* parent)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
node->Type = AML_CHUNK_LOCAL0;
|
|
node->Length = 1;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_store(AML_CHUNK* parent)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
node->Type = AML_STORE_OP;
|
|
node->Length = 1;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_byte_buffer(AML_CHUNK* parent, /* CONST*/ CHAR8* data, UINT32 size)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
INTN offset=0;
|
|
node->Type = AML_CHUNK_BUFFER;
|
|
node->Length = (UINT8)(size + 2);
|
|
node->Buffer = AllocateZeroPool (node->Length);
|
|
node->Buffer[offset++] = AML_CHUNK_BYTE; //0x0A
|
|
node->Buffer[offset++] = (CHAR8)size;
|
|
CopyMem(node->Buffer+offset, data, node->Length);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_string_buffer(AML_CHUNK* parent, /* CONST*/ CHAR8* StringBuf)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
UINTN offset=0;
|
|
UINTN len = AsciiStrLen(StringBuf);
|
|
node->Type = AML_CHUNK_BUFFER;
|
|
node->Length = (UINT8)(len + 3);
|
|
node->Buffer = AllocateZeroPool (node->Length);
|
|
node->Buffer[offset++] = AML_CHUNK_BYTE;
|
|
node->Buffer[offset++] = (CHAR8)(len + 1);
|
|
CopyMem(node->Buffer+offset, StringBuf, len);
|
|
// node->Buffer[offset+len] = '\0'; //already zero pool
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_string(AML_CHUNK* parent, /* CONST*/ CHAR8* StringBuf)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
INTN len = AsciiStrLen(StringBuf);
|
|
node->Type = AML_CHUNK_STRING;
|
|
node->Length = (UINT8)(len + 1);
|
|
node->Buffer = AllocateZeroPool (len + 1);
|
|
CopyMem(node->Buffer, StringBuf, len);
|
|
// node->Buffer[len] = '\0';
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
AML_CHUNK* aml_add_return(AML_CHUNK* parent)
|
|
{
|
|
AML_CHUNK* node = aml_create_node(parent);
|
|
|
|
if (node)
|
|
{
|
|
node->Type = AML_CHUNK_RETURN;
|
|
//aml_add_byte(node, value);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
UINT8 aml_get_size_length(UINT32 size)
|
|
{
|
|
if (size + 1 <= 0x3f)
|
|
return 1;
|
|
else if (size + 2 <= 0xfff) /* Encode in 4 bits and 1 byte */
|
|
return 2;
|
|
else if (size + 3 <= 0xfffff) /* Encode in 4 bits and 2 bytes */
|
|
return 3;
|
|
|
|
return 4; /* Encode 0xfffffff in 4 bits and 2 bytes */
|
|
}
|
|
|
|
UINT32 aml_calculate_size(AML_CHUNK* node)
|
|
{
|
|
if (node)
|
|
{
|
|
// Calculate child nodes size
|
|
AML_CHUNK* child = node->First;
|
|
UINT8 child_count = 0;
|
|
|
|
node->Size = 0;
|
|
while (child)
|
|
{
|
|
child_count++;
|
|
|
|
node->Size += (UINT16)aml_calculate_size(child);
|
|
|
|
child = child->Next;
|
|
}
|
|
|
|
switch (node->Type)
|
|
{
|
|
case AML_CHUNK_NONE:
|
|
case AML_STORE_OP:
|
|
case AML_CHUNK_LOCAL0:
|
|
node->Size += node->Length;
|
|
break;
|
|
|
|
case AML_CHUNK_METHOD:
|
|
case AML_CHUNK_SCOPE:
|
|
case AML_CHUNK_BUFFER:
|
|
node->Size += 1 + node->Length;
|
|
node->Size += aml_get_size_length(node->Size);
|
|
break;
|
|
|
|
case AML_CHUNK_DEVICE:
|
|
node->Size += 2 + node->Length;
|
|
node->Size += aml_get_size_length(node->Size);
|
|
break;
|
|
|
|
case AML_CHUNK_PACKAGE:
|
|
node->Buffer[0] = child_count;
|
|
node->Size += 1 + node->Length;
|
|
node->Size += aml_get_size_length(node->Size);
|
|
break;
|
|
|
|
case AML_CHUNK_BYTE:
|
|
if (node->Buffer[0] == 0x0 || node->Buffer[0] == 0x1)
|
|
{
|
|
node->Size += node->Length;
|
|
}
|
|
else
|
|
{
|
|
node->Size += 1 + node->Length;
|
|
}
|
|
|
|
break;
|
|
|
|
case AML_CHUNK_WORD:
|
|
case AML_CHUNK_DWORD:
|
|
case AML_CHUNK_QWORD:
|
|
case AML_CHUNK_ALIAS:
|
|
case AML_CHUNK_NAME:
|
|
case AML_CHUNK_RETURN:
|
|
case AML_CHUNK_STRING:
|
|
node->Size += 1 + node->Length;
|
|
break;
|
|
}
|
|
|
|
return node->Size;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
UINT32 aml_write_byte(UINT8 value, CHAR8* buffer, UINT32 offset)
|
|
{
|
|
buffer[offset++] = value;
|
|
|
|
return offset;
|
|
}
|
|
|
|
UINT32 aml_write_word(UINT16 value, CHAR8* buffer, UINT32 offset)
|
|
{
|
|
buffer[offset++] = value & 0xff;
|
|
buffer[offset++] = value >> 8;
|
|
|
|
return offset;
|
|
}
|
|
|
|
UINT32 aml_write_dword(UINT32 value, CHAR8* buffer, UINT32 offset)
|
|
{
|
|
buffer[offset++] = value & 0xff;
|
|
buffer[offset++] = (value >> 8) & 0xff;
|
|
buffer[offset++] = (value >> 16) & 0xff;
|
|
buffer[offset++] = (value >> 24) & 0xff;
|
|
|
|
return offset;
|
|
}
|
|
|
|
UINT32 aml_write_qword(UINT64 value, CHAR8* buffer, UINT32 offset)
|
|
{
|
|
buffer[offset++] = value & 0xff;
|
|
buffer[offset++] = RShiftU64(value, 8) & 0xff;
|
|
buffer[offset++] = RShiftU64(value, 16) & 0xff;
|
|
buffer[offset++] = RShiftU64(value, 24) & 0xff;
|
|
buffer[offset++] = RShiftU64(value, 32) & 0xff;
|
|
buffer[offset++] = RShiftU64(value, 40) & 0xff;
|
|
buffer[offset++] = RShiftU64(value, 48) & 0xff;
|
|
buffer[offset++] = RShiftU64(value, 56) & 0xff;
|
|
|
|
return offset;
|
|
}
|
|
|
|
UINT32 aml_write_buffer(CONST CHAR8* value, UINT32 size, CHAR8* buffer, UINT32 offset)
|
|
{
|
|
if (size > 0)
|
|
{
|
|
CopyMem(buffer + offset, value, size);
|
|
}
|
|
|
|
return offset + size;
|
|
}
|
|
|
|
UINT32 aml_write_size(UINT32 size, CHAR8* buffer, UINT32 offset)
|
|
{
|
|
if (size <= 0x3f) /* simple 1 byte length in 6 bits */
|
|
{
|
|
buffer[offset++] = (CHAR8)size;
|
|
}
|
|
else if (size <= 0xfff)
|
|
{
|
|
buffer[offset++] = 0x40 | (size & 0xf); /* 0x40 is type, 0x0X is first nibble of length */
|
|
buffer[offset++] = (size >> 4) & 0xff; /* +1 bytes for rest length */
|
|
}
|
|
else if (size <= 0xfffff)
|
|
{
|
|
buffer[offset++] = 0x80 | (size & 0xf); /* 0x80 is type, 0x0X is first nibble of length */
|
|
buffer[offset++] = (size >> 4) & 0xff; /* +2 bytes for rest length */
|
|
buffer[offset++] = (size >> 12) & 0xff;
|
|
}
|
|
else
|
|
{
|
|
buffer[offset++] = 0xc0 | (size & 0xf); /* 0xC0 is type, 0x0X is first nibble of length */
|
|
buffer[offset++] = (size >> 4) & 0xff; /* +3 bytes for rest length */
|
|
buffer[offset++] = (size >> 12) & 0xff;
|
|
buffer[offset++] = (size >> 20) & 0xff;
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
UINT32 aml_write_node(AML_CHUNK* node, CHAR8* buffer, UINT32 offset)
|
|
{
|
|
if (node && buffer)
|
|
{
|
|
UINT32 old = offset;
|
|
AML_CHUNK* child = node->First;
|
|
|
|
switch (node->Type)
|
|
{
|
|
case AML_CHUNK_NONE:
|
|
offset = aml_write_buffer(node->Buffer, node->Length, buffer, offset);
|
|
break;
|
|
|
|
case AML_CHUNK_LOCAL0:
|
|
case AML_STORE_OP:
|
|
offset = aml_write_byte(node->Type, buffer, offset);
|
|
break;
|
|
|
|
case AML_CHUNK_DEVICE:
|
|
offset = aml_write_byte(AML_CHUNK_OP, buffer, offset);
|
|
offset = aml_write_byte(node->Type, buffer, offset);
|
|
offset = aml_write_size(node->Size-2, buffer, offset);
|
|
offset = aml_write_buffer(node->Buffer, node->Length, buffer, offset);
|
|
break;
|
|
|
|
case AML_CHUNK_SCOPE:
|
|
case AML_CHUNK_METHOD:
|
|
case AML_CHUNK_PACKAGE:
|
|
case AML_CHUNK_BUFFER:
|
|
offset = aml_write_byte(node->Type, buffer, offset);
|
|
offset = aml_write_size(node->Size-1, buffer, offset);
|
|
offset = aml_write_buffer(node->Buffer, node->Length, buffer, offset);
|
|
break;
|
|
|
|
case AML_CHUNK_BYTE:
|
|
if (node->Buffer[0] == 0x0 || node->Buffer[0] == 0x1) {
|
|
offset = aml_write_buffer(node->Buffer, node->Length, buffer, offset);
|
|
} else {
|
|
offset = aml_write_byte(node->Type, buffer, offset);
|
|
offset = aml_write_buffer(node->Buffer, node->Length, buffer, offset);
|
|
}
|
|
break;
|
|
|
|
case AML_CHUNK_WORD:
|
|
case AML_CHUNK_DWORD:
|
|
case AML_CHUNK_QWORD:
|
|
case AML_CHUNK_ALIAS:
|
|
case AML_CHUNK_NAME:
|
|
case AML_CHUNK_RETURN:
|
|
case AML_CHUNK_STRING:
|
|
offset = aml_write_byte(node->Type, buffer, offset);
|
|
offset = aml_write_buffer(node->Buffer, node->Length, buffer, offset);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
while (child) {
|
|
offset = aml_write_node(child, buffer, offset);
|
|
child = child->Next;
|
|
}
|
|
|
|
if (offset - old != node->Size) {
|
|
MsgLog("Node size incorrect: type=0x%x size=%x offset=%x\n",
|
|
node->Type, node->Size, (offset - old));
|
|
}
|
|
}
|
|
|
|
return offset;
|
|
}
|