|
@@ -15,12 +15,244 @@
|
|
|
|
|
|
|
|
#include <iostream>
|
|
#include <iostream>
|
|
|
#include <bitset>
|
|
#include <bitset>
|
|
|
|
|
+#include <cstdint>
|
|
|
#include "data/DaFile.h"
|
|
#include "data/DaFile.h"
|
|
|
|
|
|
|
|
-DaFile::DaFile(File file){
|
|
|
|
|
|
|
+DaFile::DaFile(File file): da_file_(file){
|
|
|
|
|
+
|
|
|
|
|
+ //std::cout << "[DAFILE] " << file.GetFileName() << std::endl;
|
|
|
animations_.clear();
|
|
animations_.clear();
|
|
|
|
|
+
|
|
|
|
|
+ // Save file contents as a byte array.
|
|
|
|
|
+ file.SetOffset(0);
|
|
|
|
|
+ da_bytes_ = new u8[file.GetFileSize()];
|
|
|
|
|
+ file.GetFileBuffer(da_bytes_, 0, file.GetFileSize());
|
|
|
|
|
+ bit_offset_ = 0;
|
|
|
file.SetOffset(0);
|
|
file.SetOffset(0);
|
|
|
- Read(file);
|
|
|
|
|
|
|
+ Read();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+int DaFile::GetBitsU(int bits){
|
|
|
|
|
+ int value = 0;
|
|
|
|
|
+ int base_byte, bi, res, num_bytes, unaligned_by_bits;
|
|
|
|
|
+ int first_aligned_byte, last_aligned_byte, end_bits;
|
|
|
|
|
+ bool is_aligned, clean_end;
|
|
|
|
|
+ if (bits < 1) return value;
|
|
|
|
|
+ base_byte = bit_offset_ / 8;
|
|
|
|
|
+ unaligned_by_bits = bit_offset_ % 8;
|
|
|
|
|
+
|
|
|
|
|
+ if (unaligned_by_bits + bits > 8){
|
|
|
|
|
+ is_aligned = (unaligned_by_bits == 0);
|
|
|
|
|
+ end_bits = (bit_offset_ + bits) % 8;
|
|
|
|
|
+ clean_end = (end_bits == 0);
|
|
|
|
|
+ num_bytes =
|
|
|
|
|
+ (bits - (is_aligned ? 0 : 8 - unaligned_by_bits) - (clean_end ? 0 : end_bits)) / 8
|
|
|
|
|
+ + (is_aligned ? 0 : 1) + (clean_end ? 0 : 1);
|
|
|
|
|
+ last_aligned_byte = num_bytes - (clean_end ? 0 : 1) - 1;
|
|
|
|
|
+ first_aligned_byte = 0;
|
|
|
|
|
+
|
|
|
|
|
+ res = 0;
|
|
|
|
|
+ // Unaligned prefix
|
|
|
|
|
+ // Stored at the begining of the byte
|
|
|
|
|
+ if (!is_aligned){
|
|
|
|
|
+ res = da_bytes_[base_byte];
|
|
|
|
|
+ res &= (int) (pow(2, (8 - unaligned_by_bits)) - 1);
|
|
|
|
|
+ first_aligned_byte = 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Aligned bytes
|
|
|
|
|
+ for (bi = first_aligned_byte; bi <= last_aligned_byte; bi ++){
|
|
|
|
|
+ res *= 256;
|
|
|
|
|
+ res |= da_bytes_[base_byte + bi];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Sufix
|
|
|
|
|
+ // Stored at the end of the byte
|
|
|
|
|
+ if (!clean_end){
|
|
|
|
|
+ res *= (int) pow(2, end_bits);
|
|
|
|
|
+ res |= (
|
|
|
|
|
+ (da_bytes_[base_byte + last_aligned_byte + 1]) / (int) (pow(2, 8 - end_bits))
|
|
|
|
|
+ & (int) (pow(2, end_bits) - 1)
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else{
|
|
|
|
|
+ res = da_bytes_[base_byte];
|
|
|
|
|
+ res /= (int) pow(2, 8 - (unaligned_by_bits + bits));
|
|
|
|
|
+ res &= (int) (pow(2, bits) - 1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ value = (short)res;
|
|
|
|
|
+
|
|
|
|
|
+ bit_offset_ += bits;
|
|
|
|
|
+
|
|
|
|
|
+ return value;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+int DaFile::ExtendSignInteger(int val, int len){
|
|
|
|
|
+ int result, aux;
|
|
|
|
|
+ if ((val & (int) pow(2, (len - 1))) != 0){
|
|
|
|
|
+ aux = (int) pow(2, 16) - 1;
|
|
|
|
|
+ aux ^= (int) (pow(2, len) - 1);
|
|
|
|
|
+ aux |= val;
|
|
|
|
|
+ result = aux;
|
|
|
|
|
+ }
|
|
|
|
|
+ else result = val;
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+int DaFile::GetSignExtendedShort(int val, int len){
|
|
|
|
|
+ int result = 0;
|
|
|
|
|
+ if (len > 0){
|
|
|
|
|
+ if (len < 16) result = ExtendSignInteger(val, len);
|
|
|
|
|
+ else result = val;
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+int DaFile::GetBitsS(int bits){
|
|
|
|
|
+ int tmp = GetBitsU(bits);
|
|
|
|
|
+ return GetSignExtendedShort(tmp, bits);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+void DaFile::Read(){
|
|
|
|
|
+ int num_animations = da_file_.readU32LE();
|
|
|
|
|
+ bit_offset_ += 32;
|
|
|
|
|
+ for (int a = 0; a < num_animations; a ++){
|
|
|
|
|
+ int start_offset = bit_offset_ / 8; // In bytes.
|
|
|
|
|
+ //std::cout << " ANIMATION " << a << "/" << num_animations
|
|
|
|
|
+ // << " START: " << start_offset;
|
|
|
|
|
+ Animation anim = ReadAnimation();
|
|
|
|
|
+ if (anim.frame_count > 0) animations_.push_back(anim);
|
|
|
|
|
+ // Manually go to the next animation
|
|
|
|
|
+ bit_offset_ = (start_offset + anim.size + 12) * 8; // + 12 is for skipping the naim header.
|
|
|
|
|
+ da_file_.SetOffset(start_offset + anim.size + 12);
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+DaFile::Animation DaFile::ReadAnimation(){
|
|
|
|
|
+ Animation anim;
|
|
|
|
|
+ // Read the animation headers.
|
|
|
|
|
+ anim.bone_count = da_file_.readU32LE();
|
|
|
|
|
+ bit_offset_ += 32;
|
|
|
|
|
+ anim.frame_count = da_file_.readU32LE();
|
|
|
|
|
+ bit_offset_ += 32;
|
|
|
|
|
+ anim.size = da_file_.readU32LE();
|
|
|
|
|
+ //std::cout << " Bones: " << anim.bone_count << " Frames: " << anim.bone_count << " Size: "
|
|
|
|
|
+ // << anim.size;
|
|
|
|
|
+ // Skip small (< 11 bytes) animations.
|
|
|
|
|
+ if (anim.size < 11){
|
|
|
|
|
+ //std::cout << " SKIP" << std::endl;
|
|
|
|
|
+ //da_file_.SetOffset(da_file_.GetCurrentOffset() + anim.size);
|
|
|
|
|
+ //bit_offset_ += anim.size;
|
|
|
|
|
+ anim.frame_count = 0;
|
|
|
|
|
+ return anim;
|
|
|
|
|
+ }
|
|
|
|
|
+ //std::cout << " READ" << std::endl;
|
|
|
|
|
+ bit_offset_ += 32;
|
|
|
|
|
+ da_file_.readU32LE(); // Duplicated Frames + Size
|
|
|
|
|
+ bit_offset_ += 32;
|
|
|
|
|
+ anim.key = da_file_.readU8();
|
|
|
|
|
+ bit_offset_ += 8;
|
|
|
|
|
+ Frame frame = ReadFirstFrame(anim.bone_count, anim.key);
|
|
|
|
|
+ anim.frames.push_back(frame);
|
|
|
|
|
+ for (int i = 1; i < anim.frame_count; i ++){
|
|
|
|
|
+ frame = ReadFrame(anim.bone_count, anim.key, frame);
|
|
|
|
|
+ anim.frames.push_back(frame);
|
|
|
|
|
+ }
|
|
|
|
|
+ return anim;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+DaFile::Frame DaFile::ReadFirstFrame(u32 bones, u8 key){
|
|
|
|
|
+ Frame frame;
|
|
|
|
|
+
|
|
|
|
|
+ // Read root offset. In the first frame, always 16 bytes each.
|
|
|
|
|
+ frame.offset.s_x = GetBitsS(16);
|
|
|
|
|
+ frame.offset.s_y = GetBitsS(16);
|
|
|
|
|
+ frame.offset.s_z = GetBitsS(16);
|
|
|
|
|
+
|
|
|
|
|
+ // Turn offset to float. Any scaling that needs to be done would be done here.
|
|
|
|
|
+ frame.offset.f_x = 0;//static_cast<float>(frame.offset.s_x);
|
|
|
|
|
+ frame.offset.f_y = 0;//static_cast<float>(frame.offset.s_y) * -1;
|
|
|
|
|
+ frame.offset.f_z = 0;//static_cast<float>(frame.offset.s_z);
|
|
|
|
|
+
|
|
|
|
|
+ // Read root rotation
|
|
|
|
|
+ frame.rotation.s_x = 0;//GetBitsFromStream(12 - key) << key;
|
|
|
|
|
+ frame.rotation.s_y = 0;//GetBitsFromStream(12 - key) << key;
|
|
|
|
|
+ frame.rotation.s_z = 0;//GetBitsFromStream(12 - key) << key;
|
|
|
|
|
+
|
|
|
|
|
+ // convert rotation to integer.
|
|
|
|
|
+ frame.rotation.i_x = frame.rotation.s_x < 0 ? frame.rotation.s_x + 0x1000 : frame.rotation.s_x;
|
|
|
|
|
+ frame.rotation.i_y = frame.rotation.s_y < 0 ? frame.rotation.s_y + 0x1000 : frame.rotation.s_y;
|
|
|
|
|
+ frame.rotation.i_z = frame.rotation.s_z < 0 ? frame.rotation.s_z + 0x1000 : frame.rotation.s_z;
|
|
|
|
|
+ frame.rotation.f_x = static_cast<float>(frame.rotation.i_x) / (12 - key) * 360.0f;
|
|
|
|
|
+ frame.rotation.f_y = static_cast<float>(frame.rotation.i_y) / (12 - key) * 360.0f;
|
|
|
|
|
+ frame.rotation.f_z = static_cast<float>(frame.rotation.i_z) / (12 - key) * 360.0f;
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < bones; i ++){
|
|
|
|
|
+ TriValue bone;
|
|
|
|
|
+ // Now get each bone rotation (the first bone is actually the root, not part of the
|
|
|
|
|
+ // skeleton). During the first frame, the rotations are always (12 - key) bits. The values
|
|
|
|
|
+ // are shifted by key to align it to 12 bits.
|
|
|
|
|
+ bone.s_x = GetBitsFromStream(12 - key) << key;
|
|
|
|
|
+ bone.s_y = GetBitsFromStream(12 - key) << key;
|
|
|
|
|
+ bone.s_z = GetBitsFromStream(12 - key) << key;
|
|
|
|
|
+
|
|
|
|
|
+ // Store the INT version as the absolute value of the SHORT version.
|
|
|
|
|
+ bone.i_x = (bone.s_x < 0) ? bone.s_x + 0x1000 : bone.s_x;
|
|
|
|
|
+ bone.i_y = (bone.s_y < 0) ? bone.s_y + 0x1000 : bone.s_y;
|
|
|
|
|
+ bone.i_z = (bone.s_z < 0) ? bone.s_z + 0x1000 : bone.s_z;
|
|
|
|
|
+ bone.f_x = static_cast<float>(bone.i_x) / 4096.0f * 360.0f;
|
|
|
|
|
+ bone.f_y = static_cast<float>(bone.i_y) / 4096.0f * 360.0f;
|
|
|
|
|
+ bone.f_z = static_cast<float>(bone.i_z) / 4096.0f * 360.0f;
|
|
|
|
|
+
|
|
|
|
|
+ frame.bones.push_back(bone);
|
|
|
|
|
+ }
|
|
|
|
|
+ return frame;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+DaFile::Frame DaFile::ReadFrame(u32 bones, u8 key, Frame prev){
|
|
|
|
|
+ Frame frame;
|
|
|
|
|
+
|
|
|
|
|
+ // Get dynamic root offset and add the previous frame values.
|
|
|
|
|
+ frame.offset.s_x = GetDynamicOffsetFromStream() + prev.offset.s_x;
|
|
|
|
|
+ frame.offset.s_y = GetDynamicOffsetFromStream() + prev.offset.s_y;
|
|
|
|
|
+ frame.offset.s_z = GetDynamicOffsetFromStream() + prev.offset.s_z;
|
|
|
|
|
+ frame.offset.f_x = 0.0f;//static_cast<float>(frame.offset.s_x);
|
|
|
|
|
+ frame.offset.f_y = 0.0f;//static_cast<float>(frame.offset.s_y) * -1;
|
|
|
|
|
+ frame.offset.f_z = 0.0f;//static_cast<float>(frame.offset.s_z);
|
|
|
|
|
+
|
|
|
|
|
+ frame.rotation.s_x = 0;//GetCompressedDeltaFromStream(key) + prev.rotation.s_x;
|
|
|
|
|
+ frame.rotation.s_y = 0;//GetCompressedDeltaFromStream(key) + prev.rotation.s_y;
|
|
|
|
|
+ frame.rotation.s_z = 0;//GetCompressedDeltaFromStream(key) + prev.rotation.s_z;
|
|
|
|
|
+ frame.rotation.i_x = 0;//frame.rotation.s_x < 0 ? frame.rotation.s_x + 0x1000 : frame.rotation.s_x;
|
|
|
|
|
+ frame.rotation.i_y = 0;//frame.rotation.s_y < 0 ? frame.rotation.s_y + 0x1000 : frame.rotation.s_y;
|
|
|
|
|
+ frame.rotation.i_z = 0;//frame.rotation.s_z < 0 ? frame.rotation.s_z + 0x1000 : frame.rotation.s_z;
|
|
|
|
|
+ frame.rotation.f_x = static_cast<float>(frame.rotation.i_x) / pow(2, 12 - key) * 360.0f;
|
|
|
|
|
+ frame.rotation.f_y = static_cast<float>(frame.rotation.i_y) / pow(2, 12 - key) * 360.0f;
|
|
|
|
|
+ frame.rotation.f_z = static_cast<float>(frame.rotation.i_z) / pow(2, 12 - key) * 360.0f;
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < bones; i ++){
|
|
|
|
|
+ TriValue bone;
|
|
|
|
|
+ // The same applies here. Add the offsets and convert to INT form, adding
|
|
|
|
|
+ // 0x1000 if it is less than 0.
|
|
|
|
|
+ // When Final Fantasy VII loads these animations, it is possible for the value
|
|
|
|
|
+ // to sneak up above the 4095 boundary through a series of positive offsets.
|
|
|
|
|
+ bone.s_x = GetCompressedDeltaFromStream(key) + prev.bones[i].s_x;
|
|
|
|
|
+ bone.s_y = GetCompressedDeltaFromStream(key) + prev.bones[i].s_y;
|
|
|
|
|
+ bone.s_z = GetCompressedDeltaFromStream(key) + prev.bones[i].s_z;
|
|
|
|
|
+ bone.i_x = (bone.s_x < 0) ? bone.s_x + 0x1000 : bone.s_x;
|
|
|
|
|
+ bone.i_y = (bone.s_y < 0) ? bone.s_y + 0x1000 : bone.s_y;
|
|
|
|
|
+ bone.i_z = (bone.s_z < 0) ? bone.s_z + 0x1000 : bone.s_z;
|
|
|
|
|
+ bone.f_x = static_cast<float>(bone.i_x) / pow(2, 12 - key) * 360.0f;
|
|
|
|
|
+ bone.f_y = static_cast<float>(bone.i_y) / pow(2, 12 - key) * 360.0f;
|
|
|
|
|
+ bone.f_z = static_cast<float>(bone.i_z) / pow(2, 12 - key) * 360.0f;
|
|
|
|
|
+ frame.bones.push_back(bone);
|
|
|
|
|
+ }
|
|
|
|
|
+ return frame;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::string> DaFile::GenerateAFiles(std::string model_id, std::string path){
|
|
std::vector<std::string> DaFile::GenerateAFiles(std::string model_id, std::string path){
|
|
@@ -33,11 +265,12 @@ std::vector<std::string> DaFile::GenerateAFiles(std::string model_id, std::strin
|
|
|
while (file_index_name.size() < 2) file_index_name = "0" + file_index_name;
|
|
while (file_index_name.size() < 2) file_index_name = "0" + file_index_name;
|
|
|
std::string file_name = path + model_id + "_" + file_index_name + ".a";
|
|
std::string file_name = path + model_id + "_" + file_index_name + ".a";
|
|
|
std::ofstream a(file_name, std::ios::out | std::ios::binary);
|
|
std::ofstream a(file_name, std::ios::out | std::ios::binary);
|
|
|
|
|
+ int bone_count = animations_[anim].bone_count;
|
|
|
a.write(reinterpret_cast<const char*>(&version), 4);
|
|
a.write(reinterpret_cast<const char*>(&version), 4);
|
|
|
a.write(reinterpret_cast<const char*>(&animations_[anim].frame_count), 4);
|
|
a.write(reinterpret_cast<const char*>(&animations_[anim].frame_count), 4);
|
|
|
- a.write(reinterpret_cast<const char*>(&animations_[anim].bone_count), 4);
|
|
|
|
|
|
|
+ a.write(reinterpret_cast<const char*>(&bone_count), 4);
|
|
|
a.write(reinterpret_cast<const char*>(&rotation_order), 4);
|
|
a.write(reinterpret_cast<const char*>(&rotation_order), 4);
|
|
|
- a.write(reinterpret_cast<const char*>(&zero), 4);
|
|
|
|
|
|
|
+ a.write(reinterpret_cast<const char*>(&zero), 4); // Runtime data, skip
|
|
|
a.write(reinterpret_cast<const char*>(&zero), 4);
|
|
a.write(reinterpret_cast<const char*>(&zero), 4);
|
|
|
a.write(reinterpret_cast<const char*>(&zero), 4);
|
|
a.write(reinterpret_cast<const char*>(&zero), 4);
|
|
|
a.write(reinterpret_cast<const char*>(&zero), 4);
|
|
a.write(reinterpret_cast<const char*>(&zero), 4);
|
|
@@ -54,37 +287,23 @@ std::vector<std::string> DaFile::GenerateAFiles(std::string model_id, std::strin
|
|
|
* strange results, like the model being way offset-ed from it's position. It's likely
|
|
* strange results, like the model being way offset-ed from it's position. It's likely
|
|
|
* that Frame.position_offset has something to do with this.
|
|
* that Frame.position_offset has something to do with this.
|
|
|
*/
|
|
*/
|
|
|
- a.write(reinterpret_cast<const char*>(&zero), 4);
|
|
|
|
|
- a.write(reinterpret_cast<const char*>(&zero), 4);
|
|
|
|
|
- a.write(reinterpret_cast<const char*>(&zero), 4);
|
|
|
|
|
- a.write(reinterpret_cast<const char*>(&zero), 4);
|
|
|
|
|
- a.write(reinterpret_cast<const char*>(&zero), 4);
|
|
|
|
|
- a.write(reinterpret_cast<const char*>(&zero), 4);
|
|
|
|
|
- /*
|
|
|
|
|
- a.write(
|
|
|
|
|
- reinterpret_cast<const char*>(&animations_[anim].frames[f].position_offset.x), 4
|
|
|
|
|
- );
|
|
|
|
|
- a.write(
|
|
|
|
|
- reinterpret_cast<const char*>(&animations_[anim].frames[f].position_offset.y), 4
|
|
|
|
|
- );
|
|
|
|
|
- a.write(
|
|
|
|
|
- reinterpret_cast<const char*>(&animations_[anim].frames[f].position_offset.z), 4
|
|
|
|
|
- );
|
|
|
|
|
- */
|
|
|
|
|
- for (int b = 0; b < animations_[anim].frames[f].rotations.size(); b ++){
|
|
|
|
|
- /*if (anim == 0 && b == 0)
|
|
|
|
|
- std::cout << " FRAME " << f << " BONE 0: "
|
|
|
|
|
- << animations_[anim].frames[f].rotations[b].x << ", "
|
|
|
|
|
- << animations_[anim].frames[f].rotations[b].y << ", "
|
|
|
|
|
- << animations_[anim].frames[f].rotations[b].z << std::endl;*/
|
|
|
|
|
|
|
+ a.write(reinterpret_cast<const char*>(&animations_[anim].frames[f].rotation.i_x), 4);
|
|
|
|
|
+ a.write(reinterpret_cast<const char*>(&animations_[anim].frames[f].rotation.i_y), 4);
|
|
|
|
|
+ a.write(reinterpret_cast<const char*>(&animations_[anim].frames[f].rotation.i_z), 4);
|
|
|
|
|
+
|
|
|
|
|
+ a.write(reinterpret_cast<const char*>(&animations_[anim].frames[f].offset.f_x), 4);
|
|
|
|
|
+ a.write(reinterpret_cast<const char*>(&animations_[anim].frames[f].offset.f_y), 4);
|
|
|
|
|
+ a.write(reinterpret_cast<const char*>(&animations_[anim].frames[f].offset.f_z), 4);
|
|
|
|
|
+
|
|
|
|
|
+ for (int b = 1; b < animations_[anim].frames[f].bones.size() + 1; b ++){
|
|
|
a.write(
|
|
a.write(
|
|
|
- reinterpret_cast<const char*>(&animations_[anim].frames[f].rotations[b].x), 4
|
|
|
|
|
|
|
+ reinterpret_cast<const char*>(&animations_[anim].frames[f].bones[b].f_x), 4
|
|
|
);
|
|
);
|
|
|
- a.write(reinterpret_cast<const char*>(
|
|
|
|
|
- &animations_[anim].frames[f].rotations[b].y), 4
|
|
|
|
|
|
|
+ a.write(
|
|
|
|
|
+ reinterpret_cast<const char*>(&animations_[anim].frames[f].bones[b].f_y), 4
|
|
|
);
|
|
);
|
|
|
- a.write(reinterpret_cast<const char*>(
|
|
|
|
|
- &animations_[anim].frames[f].rotations[b].z), 4
|
|
|
|
|
|
|
+ a.write(
|
|
|
|
|
+ reinterpret_cast<const char*>(&animations_[anim].frames[f].bones[b].f_z), 4
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -94,306 +313,63 @@ std::vector<std::string> DaFile::GenerateAFiles(std::string model_id, std::strin
|
|
|
return file_list;
|
|
return file_list;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-void DaFile::Read(File file){
|
|
|
|
|
- int num_animations = file.readU32LE();
|
|
|
|
|
- for (int a = 0; a < num_animations; a ++){
|
|
|
|
|
- Animation animation;
|
|
|
|
|
- AnimationHeader header;
|
|
|
|
|
- header.bone_count = file.readU32LE();
|
|
|
|
|
- header.frame_count = file.readU32LE();
|
|
|
|
|
- animation.frame_count = header.frame_count;
|
|
|
|
|
- animation.bone_count = header.bone_count - 1;
|
|
|
|
|
- header.data_size = file.readU32LE();
|
|
|
|
|
- int next_offset = file.GetCurrentOffset() + header.data_size;
|
|
|
|
|
- if (header.data_size < 11){
|
|
|
|
|
- file.SetOffset(next_offset);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- u8* animation_data = new u8[header.data_size];
|
|
|
|
|
- for (int b = 0; b < header.data_size; b ++) animation_data[b] = file.readU8();
|
|
|
|
|
-
|
|
|
|
|
- // This will be the buffer to hold one frame. Only one frame will be stored at a time.
|
|
|
|
|
- Frame frame;
|
|
|
|
|
- Frame prev_frame;
|
|
|
|
|
- frame.SetBones(header.bone_count);
|
|
|
|
|
- int bits = 0;
|
|
|
|
|
- for (int f = 0; f < header.frame_count; f ++){
|
|
|
|
|
- FrameData frm;
|
|
|
|
|
- bits = LoadFrames(&frame, header.bone_count, bits, animation_data);
|
|
|
|
|
- // Set relative rotations to the previous frame
|
|
|
|
|
-
|
|
|
|
|
- // Reverse the Y offset (required).
|
|
|
|
|
- frame.position_offset.f_y = 0.0f - frame.position_offset.f_y;
|
|
|
|
|
- frm.position_offset.x = frame.position_offset.f_x;
|
|
|
|
|
- frm.position_offset.y = frame.position_offset.f_y;
|
|
|
|
|
- frm.position_offset.z = frame.position_offset.f_z;
|
|
|
|
|
-
|
|
|
|
|
- // Calculate final rotations
|
|
|
|
|
- for (int b = 0; b < header.bone_count - 1; b ++){
|
|
|
|
|
- frame.rotations[b].f_x
|
|
|
|
|
- = static_cast<float>(frame.rotations[b].i_x) / 4096.0f * 360.0f;
|
|
|
|
|
- frame.rotations[b].f_y
|
|
|
|
|
- = static_cast<float>(frame.rotations[b].i_y) / 4096.0f * 360.0f;
|
|
|
|
|
- frame.rotations[b].f_z
|
|
|
|
|
- = static_cast<float>(frame.rotations[b].i_z) / 4096.0f * 360.0f;
|
|
|
|
|
- Rotation rotation;
|
|
|
|
|
- rotation.x = frame.rotations[b].f_x;
|
|
|
|
|
- rotation.y = frame.rotations[b].f_y;
|
|
|
|
|
- rotation.z = frame.rotations[b].f_z;
|
|
|
|
|
- /*std::cout << "PASS " << f << ", " << b << ". prev is null? " <<
|
|
|
|
|
- (prev_frame.rotations == nullptr ? "YES" : "NO") << std::endl;
|
|
|
|
|
- if (f > 0){
|
|
|
|
|
- std::cout << " COMPENSATING START" << std::endl;
|
|
|
|
|
- rotation.x += prev_frame.rotations[b].f_x;
|
|
|
|
|
- rotation.y += prev_frame.rotations[b].f_y;
|
|
|
|
|
- rotation.z += prev_frame.rotations[b].f_z;
|
|
|
|
|
- std::cout << " COMPENSATING END" << std::endl;
|
|
|
|
|
- }*/
|
|
|
|
|
- frm.rotations.push_back(rotation);
|
|
|
|
|
- }
|
|
|
|
|
- //prev_frame = frame;
|
|
|
|
|
- animation.frames.push_back(frm);
|
|
|
|
|
-
|
|
|
|
|
- }
|
|
|
|
|
- animations_.push_back(animation);
|
|
|
|
|
- delete [] animation_data;
|
|
|
|
|
- file.SetOffset(next_offset);
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-u16 DaFile::GetValueFromStream(u8* bytes, u32 &stream_bit_offset){
|
|
|
|
|
- /*
|
|
|
|
|
- * TODO #2 I think this is doing some weird things
|
|
|
|
|
- */
|
|
|
|
|
- //std::cout << "GetValueFromStream: ";
|
|
|
|
|
- u16 value; // The return value;
|
|
|
|
|
- // The number of whole bytes already consumed in the stream.
|
|
|
|
|
- u32 stream_byte_offset = stream_bit_offset / 8;
|
|
|
|
|
- // The number of bits already consumed in the current stream byte.
|
|
|
|
|
- u32 stream_current_byte_bits_eaten = stream_bit_offset % 8;
|
|
|
|
|
- // The distance from next_stream_bytes' LSB to the key bit.
|
|
|
|
|
- u32 type_bit_shift = 7 - stream_current_byte_bits_eaten;
|
|
|
|
|
- // A copy of the next two bytes in the stream (from big-endian).
|
|
|
|
|
- u32 next_stream_bytes = bytes[stream_byte_offset] << 8 | bytes[stream_byte_offset + 1];
|
|
|
|
|
- // Test the first bit (the 'type' bit) to determine the size of the value.
|
|
|
|
|
- if (next_stream_bytes & (1 << (type_bit_shift + 8))){ // Sixteen-bit value:
|
|
|
|
|
- // Collect one more byte from the stream.
|
|
|
|
|
- next_stream_bytes = next_stream_bytes << 8 | bytes[stream_byte_offset + 2];
|
|
|
|
|
- // Shift the delta value into place.
|
|
|
|
|
- value = (next_stream_bytes << (stream_current_byte_bits_eaten + 1)) >> 8;
|
|
|
|
|
- // Update the stream offset.
|
|
|
|
|
- stream_bit_offset += 17;
|
|
|
|
|
- //std::cout << " 17 bits: " << value << std::endl;
|
|
|
|
|
- }
|
|
|
|
|
- else{ // Seven-bit value
|
|
|
|
|
- // Shift the delta value into place (taking care to preserve the sign).
|
|
|
|
|
- value = ((short)(next_stream_bytes << (stream_current_byte_bits_eaten + 1))) >> 9;
|
|
|
|
|
- // Update the stream offset.
|
|
|
|
|
- stream_bit_offset += 8;
|
|
|
|
|
- //std::cout << " 8 bits: " << value << std::endl;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Return the value.
|
|
|
|
|
|
|
+u16 DaFile::GetDynamicOffsetFromStream(){
|
|
|
|
|
+ u16 value;
|
|
|
|
|
+ int control_bit = GetBitsS(1);
|
|
|
|
|
+ if (control_bit == 1) value = GetBitsS(16);
|
|
|
|
|
+ else value = GetBitsS(7);
|
|
|
return value;
|
|
return value;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-u16 DaFile::ReadU16LE(u8* bytes, u32 &stream_bit_offset){
|
|
|
|
|
- u32 byte_offset = stream_bit_offset / 8;
|
|
|
|
|
- u16 data = (bytes + byte_offset)[0] | ((bytes + byte_offset)[1] << 8);
|
|
|
|
|
- stream_bit_offset += 16;
|
|
|
|
|
|
|
+u16 DaFile::ReadU16LE(){
|
|
|
|
|
+ u32 byte_offset = bit_offset_ / 8;
|
|
|
|
|
+ u16 data = (da_bytes_ + byte_offset)[0] | ((da_bytes_ + byte_offset)[1] << 8);
|
|
|
|
|
+ bit_offset_ += 16;
|
|
|
return data;
|
|
return data;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-int DaFile::GetBitsFromStream(u8* bytes, u32 &stream_bit_offset, int bits){
|
|
|
|
|
|
|
+int DaFile::GetBitsFromStream(int bits){
|
|
|
int value = 0;
|
|
int value = 0;
|
|
|
- unsigned int byte_offset = stream_bit_offset / 8;
|
|
|
|
|
- unsigned int bit_offset = stream_bit_offset % 8;
|
|
|
|
|
- //std::cout << "GetBitsFromStream: " << bits << " bits from " << stream_bit_offset
|
|
|
|
|
- // << ": (" << (int)bytes[byte_offset] << " " << (int)bytes[byte_offset + 1]
|
|
|
|
|
- // << "):\t ";
|
|
|
|
|
-
|
|
|
|
|
|
|
+ unsigned int byte_offset = bit_offset_ / 8;
|
|
|
|
|
+ unsigned int bit_offset = bit_offset_ % 8;
|
|
|
for (int b = 0; b < bits; b ++){
|
|
for (int b = 0; b < bits; b ++){
|
|
|
- u8 byte = bytes[byte_offset + (bit_offset + b) / 8];
|
|
|
|
|
|
|
+ u8 byte = da_bytes_[byte_offset + (bit_offset + b) / 8];
|
|
|
int bit = (byte >> ((bit_offset + 7 - b) % 8) & 0x01);
|
|
int bit = (byte >> ((bit_offset + 7 - b) % 8) & 0x01);
|
|
|
- //std::cout << " ["<< (int)byte <<":" << (int)((bit_offset + 7 - b) % 8) << "]" << bit;
|
|
|
|
|
value += bit * (pow(2, bits - 1 - b));
|
|
value += bit * (pow(2, bits - 1 - b));
|
|
|
- stream_bit_offset += 1;
|
|
|
|
|
|
|
+ bit_offset_ += 1;
|
|
|
}
|
|
}
|
|
|
return value;
|
|
return value;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-u16 DaFile::GetCompressedDeltaFromStream(
|
|
|
|
|
- u8* bytes, u32& stream_bit_offset, int lowered_precision_bits
|
|
|
|
|
-){
|
|
|
|
|
|
|
+int DaFile::GetCompressedDeltaFromStream(int lowered_precision_bits){
|
|
|
unsigned int bits;
|
|
unsigned int bits;
|
|
|
- int first_bit = GetBitsFromStream(bytes, stream_bit_offset, 1);
|
|
|
|
|
- if (first_bit){
|
|
|
|
|
- unsigned int key = GetBitsFromStream(bytes, stream_bit_offset, 3) & 7;
|
|
|
|
|
- int temp;
|
|
|
|
|
|
|
+ int first_bit = GetBitsU(1);
|
|
|
|
|
+ if (first_bit == 1){
|
|
|
|
|
+ unsigned int key = GetBitsU(3);// & 7;
|
|
|
|
|
+ int temp, sign, orig_temp;
|
|
|
|
|
+ unsigned int msb_mask;
|
|
|
switch (key){
|
|
switch (key){
|
|
|
case 0: // Return the smallest possible decrement delta (at given precision).
|
|
case 0: // Return the smallest possible decrement delta (at given precision).
|
|
|
- //std::cout << " GCDFS 0: " << (-1 << lowered_precision_bits) << std::endl;
|
|
|
|
|
return (-1 << lowered_precision_bits);
|
|
return (-1 << lowered_precision_bits);
|
|
|
case 1: case 2: case 3: case 4: case 5: case 6:
|
|
case 1: case 2: case 3: case 4: case 5: case 6:
|
|
|
bits = key;
|
|
bits = key;
|
|
|
// Read a corresponding number of bits from the stream.
|
|
// Read a corresponding number of bits from the stream.
|
|
|
- temp = GetBitsFromStream(bytes, stream_bit_offset, bits);
|
|
|
|
|
- //std::cout << " GCDFS " << key << ": ";
|
|
|
|
|
- // Transform the value into the full seven-bit value, using the bit length
|
|
|
|
|
- // as part of the encoding scheme (see notes).
|
|
|
|
|
- if (temp < 0) temp -= 1 << (bits - 1);
|
|
|
|
|
- else temp += 1 << (bits - 1);
|
|
|
|
|
- //std::cout << temp << " -> ";
|
|
|
|
|
- // Adapt to the requested precision and return.
|
|
|
|
|
- //std::cout << (temp << lowered_precision_bits) << std::endl;
|
|
|
|
|
- return (temp << lowered_precision_bits);
|
|
|
|
|
|
|
+ temp = (int16_t) GetBitsS(bits);
|
|
|
|
|
+ // Invert the value of the last bit
|
|
|
|
|
+ sign = (int) pow(2, bits - 1);
|
|
|
|
|
+
|
|
|
|
|
+ if (temp < 0) temp -= sign;
|
|
|
|
|
+ else temp += sign;
|
|
|
|
|
+ // Convert to 12-bits value
|
|
|
|
|
+ temp *= (int) pow(2, lowered_precision_bits);
|
|
|
|
|
+ return temp;
|
|
|
case 7:
|
|
case 7:
|
|
|
// Read an uncompressed value from the stream (at requested precision),
|
|
// Read an uncompressed value from the stream (at requested precision),
|
|
|
// and return.
|
|
// and return.
|
|
|
- temp = GetBitsFromStream(
|
|
|
|
|
- bytes, stream_bit_offset, 12 - lowered_precision_bits
|
|
|
|
|
- );
|
|
|
|
|
- //std::cout << " GCDFS 7: " << temp << " -> " << (temp << lowered_precision_bits)
|
|
|
|
|
- // << std::endl;
|
|
|
|
|
|
|
+ temp = (int16_t) GetBitsS(12 - lowered_precision_bits);
|
|
|
return (temp << lowered_precision_bits);
|
|
return (temp << lowered_precision_bits);
|
|
|
- //default:
|
|
|
|
|
- //std::cout << " GCDFS " << key << " UNKNOWN " << std::endl;
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
// Default/error: return zero.
|
|
// Default/error: return zero.
|
|
|
- //std::cout << " GCDFS 0 --"<< std::endl;
|
|
|
|
|
- return 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-u32 DaFile::LoadFrames(Frame* frame, int bones, int bit_start, u8* animation_buffer){
|
|
|
|
|
- // Get backups of the information we need.
|
|
|
|
|
- u32 orig_bit_start = bit_start;
|
|
|
|
|
- int orig_bones = bones;
|
|
|
|
|
- u8* orig_animation_buffer = animation_buffer;
|
|
|
|
|
- AnimationMiniHeader* mini_header = (AnimationMiniHeader*) animation_buffer;
|
|
|
|
|
- u16 data_size = mini_header->data_size;
|
|
|
|
|
- u8 key = mini_header->key;
|
|
|
|
|
-
|
|
|
|
|
- // Skip the first 5 bytes because they are part of the frame header.
|
|
|
|
|
- orig_animation_buffer += sizeof(AnimationMiniHeader);
|
|
|
|
|
-
|
|
|
|
|
- if (bit_start == 0){ // First frame?
|
|
|
|
|
-
|
|
|
|
|
- // Skip the next 6 bytes. Why? I don't know what they are.
|
|
|
|
|
- orig_bit_start += 8 * 8;
|
|
|
|
|
-
|
|
|
|
|
- // The first frame is uncompressed and each value is the actual rotation.
|
|
|
|
|
- //std::cout << "FIRSR TRAME READING AT " << orig_bit_start;
|
|
|
|
|
- frame->position_offset.s_x = GetBitsFromStream(orig_animation_buffer, orig_bit_start, 16);
|
|
|
|
|
- //= ReadU16LE(orig_animation_buffer, orig_bit_start);
|
|
|
|
|
- //std::cout << ", " << orig_bit_start;
|
|
|
|
|
- frame->position_offset.s_y = GetBitsFromStream(orig_animation_buffer, orig_bit_start, 16);
|
|
|
|
|
- //= ReadU16LE(orig_animation_buffer, orig_bit_start);
|
|
|
|
|
- //std::cout << ", " << orig_bit_start;
|
|
|
|
|
- frame->position_offset.s_z = GetBitsFromStream(orig_animation_buffer, orig_bit_start, 16);
|
|
|
|
|
- //= ReadU16LE(orig_animation_buffer, orig_bit_start);
|
|
|
|
|
- //std::cout << " END AT " << orig_bit_start << "\n";
|
|
|
|
|
-
|
|
|
|
|
- // This function will set the FLOAT values for the positions.
|
|
|
|
|
- // Any scaling that needs to be done would be done here.
|
|
|
|
|
- frame->position_offset.f_x = static_cast<float>(frame->position_offset.s_x);
|
|
|
|
|
- frame->position_offset.f_y = static_cast<float>(frame->position_offset.s_y);
|
|
|
|
|
- frame->position_offset.f_z = static_cast<float>(frame->position_offset.s_z);
|
|
|
|
|
-
|
|
|
|
|
- // TODO: For first bone, go back 2*3 bytes, and reread them, but this time as
|
|
|
|
|
- // little endian for the rotations.
|
|
|
|
|
- orig_bit_start -= (2 * 3 * 8);
|
|
|
|
|
-
|
|
|
|
|
- frame->rotations[0].s_x = ReadU16LE(orig_animation_buffer, orig_bit_start);
|
|
|
|
|
- frame->rotations[0].s_y = ReadU16LE(orig_animation_buffer, orig_bit_start);
|
|
|
|
|
- frame->rotations[0].s_z = ReadU16LE(orig_animation_buffer, orig_bit_start);
|
|
|
|
|
- frame->rotations[0].i_x = (frame->rotations[0].s_x < 0) ?
|
|
|
|
|
- frame->rotations[0].s_x + 0x1000 : frame->rotations[0].s_x;
|
|
|
|
|
- frame->rotations[0].i_y = (frame->rotations[0].s_y < 0) ?
|
|
|
|
|
- frame->rotations[0].s_y + 0x1000 : frame->rotations[0].s_y;
|
|
|
|
|
- frame->rotations[0].i_z = (frame->rotations[0].s_z < 0) ?
|
|
|
|
|
- frame->rotations[0].s_z + 0x1000 : frame->rotations[0].s_z;
|
|
|
|
|
-
|
|
|
|
|
- //std::cout << "FIRST FRAME FIRST BONE: "
|
|
|
|
|
- // << frame->rotations[0].i_x << " (" << (int) frame->rotations[0].s_x << "), "
|
|
|
|
|
- // << frame->rotations[0].i_y << " (" << (int) frame->rotations[0].s_y << "), "
|
|
|
|
|
- // << frame->rotations[0].i_z << " (" << (int) frame->rotations[0].s_z << ")\n";
|
|
|
|
|
-
|
|
|
|
|
- for (int i = 1; i < orig_bones - 1; i ++){
|
|
|
|
|
- // Now get each bone rotation (the first bone is actually the root, not part of
|
|
|
|
|
- // the skeleton). During the first frame, the rotations are always (12 - key).
|
|
|
|
|
- // bits. The values are shifted by key to align it to 12 bits.
|
|
|
|
|
- frame->rotations[i].s_x
|
|
|
|
|
- = GetBitsFromStream(orig_animation_buffer, orig_bit_start, 12 - key) << key;
|
|
|
|
|
- frame->rotations[i].s_y
|
|
|
|
|
- = GetBitsFromStream(orig_animation_buffer, orig_bit_start, 12 - key) << key;
|
|
|
|
|
- frame->rotations[i].s_z
|
|
|
|
|
- = GetBitsFromStream(orig_animation_buffer, orig_bit_start, 12 - key) << key;
|
|
|
|
|
-
|
|
|
|
|
- // Store the INT version as the absolute value of the SHORT version.
|
|
|
|
|
- frame->rotations[i].i_x = (frame->rotations[i].s_x < 0) ?
|
|
|
|
|
- frame->rotations[i].s_x + 0x1000 : frame->rotations[i].s_x;
|
|
|
|
|
- frame->rotations[i].i_y = (frame->rotations[i].s_y < 0) ?
|
|
|
|
|
- frame->rotations[i].s_y + 0x1000 : frame->rotations[i].s_y;
|
|
|
|
|
- frame->rotations[i].i_z = (frame->rotations[i].s_z < 0) ?
|
|
|
|
|
- frame->rotations[i].s_z + 0x1000 : frame->rotations[i].s_z;
|
|
|
|
|
-
|
|
|
|
|
- /*if (i < 3){
|
|
|
|
|
- std::cout << "FIRST FRAME BONE " << i << ": "
|
|
|
|
|
- << frame->rotations[i].i_x << " (" << (int) frame->rotations[i].s_x << "), "
|
|
|
|
|
- << frame->rotations[i].i_y << " (" << (int) frame->rotations[i].s_y << "), "
|
|
|
|
|
- << frame->rotations[i].i_z << " (" << (int) frame->rotations[i].s_z << ")\n";
|
|
|
|
|
- }*/
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- else { // All other frames.
|
|
|
|
|
- u16 x, y, z; // Get the positional offsets.
|
|
|
|
|
- x = GetValueFromStream(orig_animation_buffer, orig_bit_start);
|
|
|
|
|
- y = GetValueFromStream(orig_animation_buffer, orig_bit_start);
|
|
|
|
|
- z = GetValueFromStream(orig_animation_buffer, orig_bit_start);
|
|
|
|
|
-
|
|
|
|
|
- // When reaching this area of the function, frame will have the previous frame
|
|
|
|
|
- // still stored in it. Just add the offsets.
|
|
|
|
|
- frame->position_offset.s_x += x;
|
|
|
|
|
- frame->position_offset.s_y += y;
|
|
|
|
|
- frame->position_offset.s_z += z;
|
|
|
|
|
- frame->position_offset.f_x = static_cast<float>(frame->position_offset.s_x);
|
|
|
|
|
- frame->position_offset.f_y = static_cast<float>(frame->position_offset.s_y);
|
|
|
|
|
- frame->position_offset.f_z = static_cast<float>(frame->position_offset.s_z);
|
|
|
|
|
- for (int i = 0; i < orig_bones; i ++){
|
|
|
|
|
-
|
|
|
|
|
- // The same applies here. Add the offsets and convert to INT form, adding
|
|
|
|
|
- // 0x1000 if it is less than 0.
|
|
|
|
|
- // When Final Fantasy VII loads these animations, it is possible for the value
|
|
|
|
|
- // to sneak up above the 4095 boundary through a series of positive offsets.
|
|
|
|
|
-
|
|
|
|
|
- // TODO: This is NOT a fix. third parameter should be key, not 12 - key.
|
|
|
|
|
- // The reason this looks better is because the rotations are now made so small they are
|
|
|
|
|
- // imperceptible
|
|
|
|
|
- x = GetCompressedDeltaFromStream(orig_animation_buffer, orig_bit_start, 12 - key);
|
|
|
|
|
- y = GetCompressedDeltaFromStream(orig_animation_buffer, orig_bit_start, 12 - key);
|
|
|
|
|
- z = GetCompressedDeltaFromStream(orig_animation_buffer, orig_bit_start, 12 - key);
|
|
|
|
|
-
|
|
|
|
|
- frame->rotations[i].s_x += x;
|
|
|
|
|
- frame->rotations[i].s_y += y;
|
|
|
|
|
- frame->rotations[i].s_z += z;
|
|
|
|
|
- //if (i == 0)
|
|
|
|
|
- // std::cout << " ADDED ROTATION: " << frame->rotations[i].s_x << ", "
|
|
|
|
|
- // << frame->rotations[i].s_y << ", " << frame->rotations[i].s_z << std::endl;
|
|
|
|
|
- frame->rotations[i].i_x = (frame->rotations[i].s_x < 0) ?
|
|
|
|
|
- frame->rotations[i].s_x + 0x1000 : frame->rotations[i].s_x;
|
|
|
|
|
- frame->rotations[i].i_y = (frame->rotations[i].s_y < 0) ?
|
|
|
|
|
- frame->rotations[i].s_y + 0x1000 : frame->rotations[i].s_y;
|
|
|
|
|
- frame->rotations[i].i_z = (frame->rotations[i].s_z < 0) ?
|
|
|
|
|
- frame->rotations[i].s_z + 0x1000 : frame->rotations[i].s_z;
|
|
|
|
|
- //if (i == 0)
|
|
|
|
|
- // std::cout << " PROCESSED ROTATION: " << frame->rotations[i].i_x << ", "
|
|
|
|
|
- // << frame->rotations[i].i_y << ", " << frame->rotations[i].i_z << std::endl;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // If not as many bits as there are in the frame are read, return the location where
|
|
|
|
|
- // the bits should start for the next frame. Otherwise, return 0.
|
|
|
|
|
- if (orig_bit_start / 8 < data_size) return orig_bit_start;
|
|
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|