private static int EncodeMaskGroup(SYMBHeader *symb, SYMBMaskHeader *header, List <RSAREntryNode> gList, RSARNode n, int grp) { int[] stringIds = gList.Select(x => x._rebuildStringId).Where(x => x >= 0).ToArray(); SYMBMaskEntry.Build(stringIds, symb, header, header->Entries); return(SYMBMaskHeader.Size + (stringIds.Length * 2 - 1) * SYMBMaskEntry.Size); }
//Code written by Mawootad public static void Build(int[] indices, SYMBHeader *header, SYMBMaskHeader *maskHeader, SYMBMaskEntry *entries) { //initialization maskHeader->_rootId = 0; maskHeader->_numEntries = 0; //Loop over indicies and add them. This seems to be roughly how the file is normally built, as it has the same resulting leaf-node-leaf-node pattern foreach (int id in indices) { AddToTrie(entries, maskHeader, id, header); } }
//Failed attempts at generating symb ids commented out below, enjoy //static void GenIds(SYMBHeader* symb, SYMBMaskHeader* header, int index, ushort allowedBit) //{ // SYMBMaskEntry* first = &header->Entries[index]; // string mainName = symb->GetStringEntry(first->_stringId); // for (int i = 1; i < header->_numEntries; i += 2) // { // SYMBMaskEntry* secName = &header->Entries[i]; // SYMBMaskEntry* secBit = &header->Entries[i + 1]; // if (i == index || secBit->_bit != allowedBit) // continue; // string compName = symb->GetStringEntry(secName->_stringId); // int bitIndex = mainName.CompareBits(compName); // if (bitIndex >= 0) // { // //Set the bit index // secBit->_bit = (ushort)bitIndex; // int bit = bitIndex % 8; // int byteIndex = (bitIndex - bit) / 8; // bool leftFound = false, rightFound = false; // mainName = compName; // //Keeping looking down the list for the left and right entries // for (int x = i + 2; x < header->_numEntries; x += 2) // { // SYMBMaskEntry* lrName = &header->Entries[x]; // SYMBMaskEntry* lrBit = &header->Entries[x + 1]; // compName = symb->GetStringEntry(lrName->_stringId); // if (x == index || lrBit->_bit != allowedBit) // continue; // bool forceLeft = false; // if (byteIndex >= Math.Min(mainName.Length, compName.Length)) // forceLeft = true; // if (forceLeft || mainName.AtBit(bitIndex) != compName.AtBit(bitIndex)) // { // if (leftFound) // continue; // leftFound = true; // secBit->_leftId = x + 1; // GenIds(symb, header, x, lrBit->_bit); // } // else // { // if (rightFound) // continue; // rightFound = true; // secBit->_rightId = x + 1; // GenIds(symb, header, x, lrBit->_bit); // } // } // if (!leftFound) //No strings matched // secBit->_leftId = i; // else if (!rightFound) //All strings matched // secBit->_rightId = i; // break; // } // } //} //String Table to convert to a tree //string[] stringTable = new string[] { "SDPLAYER_BGM", "SDPLAYER_SE", "SDPLAYER_VOICE", "SDPLAYER_SYSTEM", "SDPLAYER_LOOP", "SDPLAYER_VOICE2", "SDPLAYER_SE_NOEFFECT" }; //public class TEntry //{ // public string _string; // public string _binary; // public int _bit = -1; // public int _leftID = -1; // public int _rightID = -1; // public int _stringID; // public int _id; // public TEntry(RSAREntryState s) // { // _binary = (_string = s._node._fullPath).ToBinaryArray(); // _stringID = s._stringId; // _id = s._index; // } //} //Making the output table to save me buttloads of time >.> //public void convertToBinary(string): // ''' Converts a text string to a string representation of itself in binary. ''' // return ''.join(['%08d'%int(bin(ord(i))[2:]) for i in string]) //Populating an empty table for my own convenience. //I assume the stringlist is ordered by ID, and is the first and only segment in the symb. //Cause I'm a lazy bastard. //Normal String, String (in binary), bit, LeftID, RightID, StringID, ID //outputTable = [{'string':stringTable[x], 'binary':convertToBinary(stringTable[x]), // 'bit':-1, 'LeftID':-1, 'RightID':-1, 'StringID':x, 'ID':x} for x in xrange(len(stringTable))] //private static void WriteData(List<TEntry> Left, List<TEntry> Right, int bit) //{ // //Writes all the necessary information to the node // TEntry currentNode = Right[0]; // //Write Position // currentNode._bit = bit; // //Write the left branch (matching) This is a bit fugly, because of how I handle the output table. // //I basically determine the 'line' based off the ID. // if (Left.Count > 1) // currentNode._leftID = Left[0]._id * 2; // else // { // currentNode._leftID = Left[0]._id * 2 - 1; // //Edge case for the first node, who has no -1 line. // if (currentNode._leftID < 0) // currentNode._leftID = 0; // } // if (Right.Count > 1) // currentNode._rightID = Right[1]._id * 2; // else // { // currentNode._rightID = Right[0]._id * 2 - 1; // //Edge case for the first node, who has no -1 line. // if (currentNode._rightID < 0) // currentNode._rightID = 0; // } //} //private static void SplitTable(List<TEntry> outputTable, out List<TEntry> Left, out List<TEntry> Right) //{ // //Splits the table in two and writes the data // TEntry originalNode = outputTable[0]; // List<TEntry> Temp; // //Iterate bit by bit // int bit = 0, entry = 0; // for (bit = 0; bit < originalNode._binary.Length; bit++) // { // char compareBit = originalNode._binary[bit]; // //Go over the table // for (entry = 0; entry < outputTable.Count; entry++) // { // //F*****g Ugly edge case - initial string is too long to compare // if (bit >= outputTable[entry]._binary.Length) // { // //Python stuff to change the 'start' of the table to the current position // Temp = outputTable.ShiftFirst(entry); // //Split the ordered table there into Left (matching) and right (non-matching) // Left = new List<TEntry> { outputTable[entry] }; // Right = new List<TEntry>(); // foreach (TEntry t in Temp) // if (t._id != outputTable[entry]._id) // Right.Add(t); // WriteData(Left, Right, bit); // //It splits! // return; // } // //This is the normal case - applies 90% of the time, I find. // if (outputTable[entry]._binary[bit] != compareBit) // { // //Python stuff to change the 'start' of the table to the current position // Temp = outputTable.ShiftFirst(entry); // //Split the ordered table there into Left (matching) and right (non-matching) // Left = new List<TEntry>(); // foreach (TEntry t in Temp) // if (t._binary[bit] == '0') // Left.Add(t); // Right = new List<TEntry>(); // foreach (TEntry t in Temp) // if (t._binary[bit] == '1') // Right.Add(t); // WriteData(Left, Right, bit); // //It splits! // return; // } // } // } // //If it got this far, then it ran out of bits. // //So now we gotta // //Split the ordered table there into Left (matching) and right (non-matching) // Left = new List<TEntry> { originalNode }; // Right = new List<TEntry>(); // foreach (TEntry t in outputTable) // if (t._id != originalNode._id) // Right.Add(t); // WriteData(Left, Right, bit); // //It splits! // return; //} //public static void CalcMatch(List<TEntry> outputTable) //{ // //Split the Table by node // List<TEntry> Left, Right; // SplitTable(outputTable, out Left, out Right); // //foreach (TEntry t in Left) // // Console.WriteLine(String.Format("Left: {0} {1} {2} {3} ", t._string, t._bit, t._leftID, t._rightID)); // //Console.WriteLine("-------------------------------------------------"); // //foreach (TEntry t in Right) // // Console.WriteLine(String.Format("Right: {0} {1} {2} {3} ", t._string, t._bit, t._leftID, t._rightID)); // if (Left.Count > 1) // CalcMatch(Left); // if (Right.Count > 1) // CalcMatch(Right); //} //public class PatriciaTree //{ // public string[] strings; // public string[] bin_strings; // public int out_node_idx = 0; // public PatriciaTree(string[] str) // { // strings = str; // bin_strings = strings.Select(x => x.ToBinaryArray()).ToArray(); // } // public int[] partition_tree(List<int> idx_list, int position) // { // List<int> left = new List<int>(), right = new List<int>(); // for (int i = 0; i < idx_list.Count; i++ ) // { // string bstr = bin_strings[i]; // if (position >= bstr.Length || position < 0) // left.Add(i); // else // { // char ch = bstr[position]; // if (ch == '1') // right.Add(i); // else // left.Add(i); // } // } // int total = left.Count + right.Count; // if (total == 1) // { // //This is a leaf, woo // int pos = -1, t; // if (left.Count > 0) // t = left[0]; // else // t = right[0]; // return new int[] { pos, t }; // } // else if (left.Count == 0 || right.Count == 0) // //This is not a branch, let's try the next bit // if (left.Count > 0) // return partition_tree(left, position + 1); // else // return partition_tree(right, position + 1); // else // { // int[] l = partition_tree(left, position + 1); // int[] r = partition_tree(right, position + 1); // return new int[] { position }.Append(l).Append(r); // } // } // public int tree_size(int[] node) // { // //Node is the return from partition_tree() // if (node[0] == -1) // return 1; // else // return 1 + tree_size(new int[] { node[1] }) + tree_size(new int[] { node[2] }); // } // public void dump_tree(int[] tree) // { // out_node_idx = 0; // _dump_node(tree); // } // public void _dump_node(int[] node) // { // //Node is the return from partition_tree() // int bit_id = node[0]; // if (bit_id == -1) // { // //We're packing a leaf // Console.WriteLine(String.Format("{0} leaf : {1}", out_node_idx, strings[node[1]])); // out_node_idx += 1; // } // else // { // //We're packing a branch // int left_size = tree_size(new int[] { node[1] }); // int left_idx = out_node_idx + 1; // int right_idx = left_idx + left_size; // Console.WriteLine(String.Format("{0} branch: bit={1} left={2} right={3}", out_node_idx, bit_id, left_idx, right_idx)); // out_node_idx += 1; // dump_tree(new int[] { node[1] }); // dump_tree(new int[] { node[2] }); // } // } //} //tree = PatriciaTree(('BANK_HOMEBUTTON','BANK_SYSTEM_SE','BANK_BGM','BANK_SOFTWARE_KEYBOARD_SE')) //print(tree.partition_tree()) //tree.dump_tree(tree.partition_tree()) private static int EncodeMaskGroup(SYMBHeader *symb, SYMBMaskHeader *header, List <RSAREntryState> group, RSARNode n, int grp) { SYMBMaskEntry *entry = header->Entries; //List<TEntry> outputTable = new List<TEntry>(); int i = 0; foreach (RSAREntryState s in group) { entry[i++] = new SYMBMaskEntry(1, -1, -1, -1, s._stringId, s._index); if (s._index != 0) { //entry[i++] = new SYMBMaskEntry(0, 0, 0, 0, -1, -1); entry[i] = n._symbCache[grp][i++]; } //outputTable.Add(new TEntry(s)); } header->_numEntries = group.Count * 2 - 1; header->_rootId = n._rootIds[grp]; //GenIds(symb, header, 0, 0); //CalcMatch(outputTable); //foreach (TEntry t in outputTable) //{ // *entry++ = new SYMBMaskEntry(1, -1, -1, -1, t._stringID, t._id); // if (t._id != 0) // *entry++ = new SYMBMaskEntry(0, (short)t._bit, t._leftID, t._rightID, -1, -1); //} //PatriciaTree t = new PatriciaTree(group.Select(x => x._node._fullPath).ToArray()); //int[] p = t.partition_tree(new List<int>(t.strings.Length), 0); //t.dump_tree(p); int len = 8 + i * SYMBMaskEntry.Size; //int rootId = 0; //int lowestBit = int.MaxValue; //entry = header->Entries; //for (int i = 2; i < header->_numEntries; i += 2) //{ // if (entry[i]._bit < lowestBit) // { // lowestBit = entry[i]._bit; // rootId = i; // } //} //header->_rootId = rootId; return(len); }
private static int EncodeMaskGroup(SYMBMaskHeader *header, List <RSAREntryState> group) { header->_entrySize = 0xA; header->_entryNum = group.Count * 2 - 1; SYMBMaskEntry *entry = header->Entries; foreach (RSAREntryState s in group) { *entry++ = new SYMBMaskEntry(0x1FFFF, -1, -1, s._stringId, s._index); if (s._index != 0) { *entry++ = new SYMBMaskEntry(0, 0, 0, -1, -1); } } return((int)entry - (int)header); }
public override void OnPopulate() { //Enumerate entries, attaching them to the files. RSARHeader *rsar = Header; SYMBHeader *symb = rsar->SYMBBlock; sbyte * offset = (sbyte *)symb + 8; buint * stringOffsets = symb->StringOffsets; VoidPtr baseAddr = (VoidPtr)rsar->INFOBlock + 8; ruint * typeList = (ruint *)baseAddr; //Iterate through group types for (int i = 0; i < 5; i++) { _infoCache[i] = new List <RSAREntryNode>(); Type t = null; RuintList *list = (RuintList *)((uint)baseAddr + typeList[i]); sbyte * str, end; switch (i) { case 0: t = typeof(RSARSoundNode); break; case 1: t = typeof(RSARBankNode); break; case 2: t = typeof(RSARPlayerInfoNode); break; case 3: continue; //Files case 4: t = typeof(RSARGroupNode); break; //Last group entry is null } for (int x = 0; x < list->_numEntries; x++) { VoidPtr addr = list->Get(baseAddr, x); ResourceNode parent = this; RSAREntryNode n = Activator.CreateInstance(t) as RSAREntryNode; n._origSource = n._uncompSource = new DataSource(addr, 0); n._infoIndex = x; if (i == 4 && x == list->_numEntries - 1) { n._name = "<null>"; n._parent = this; _nullGroup = n as RSARGroupNode; } else { str = offset + stringOffsets[n.StringId]; for (end = str; *end != 0; end++) { ; } while ((--end > str) && (*end != '_')) { ; } if (end > str) { parent = CreatePath(parent, str, (int)end - (int)str); n._name = new String(end + 1); } else { n._name = new String(str); } } n.Initialize(parent, addr, 0); _infoCache[i].Add(n); } } ftr = *(INFOFooter *)((uint)baseAddr + typeList[5]); foreach (RSARFileNode n in Files) { if (!(n is RSARExtFileNode)) { n.GetName(); } } _rootIds = new int[4]; _symbCache = new List <SYMBMaskEntry> [4]; bint *offsets = (bint *)((VoidPtr)symb + 12); for (int i = 0; i < 4; i++) { _symbCache[i] = new List <SYMBMaskEntry>(); SYMBMaskHeader *hdr = (SYMBMaskHeader *)((VoidPtr)symb + 8 + offsets[i]); //Console.WriteLine("Root Index = " + hdr->_rootId); _rootIds[i] = hdr->_rootId; for (int x = 0; x < hdr->_numEntries; x++) { SYMBMaskEntry *e = &hdr->Entries[x]; _symbCache[i].Add(*e); //Console.WriteLine(String.Format("[{5}] {0}, {1}, {2} - {4}", e->_bit != -1 ? e->_bit.ToString().PadLeft(3) : " ", e->_leftId != -1 ? e->_leftId.ToString().PadLeft(3) : " ", e->_rightId != -1 ? e->_rightId.ToString().PadLeft(3) : " ", e->_index != -1 ? e->_index.ToString().PadLeft(3) : " ", new string(offset + stringOffsets[e->_stringId]), x.ToString().PadLeft(3))); } } //Sort(true); }
private static void AddToTrie(SYMBMaskEntry *trie, SYMBMaskHeader *head, int id, SYMBHeader *table) { //If list is empty add the node and quit if (head->_numEntries == 0) { trie[head->_numEntries++] = new SYMBMaskEntry(1, -1, -1, -1, id, 0); return; } string value = table->GetStringEntry(id); //String.IsNullOrEmpty(value) if ((value ?? "") == "") { throw new ArgumentException("String is null or whitespace"); } SYMBMaskEntry search = trie[head->_rootId]; List <int> path = new List <int> { head->_rootId }; //Find the string that matches the current string in the trie. Needs to be done in order to determine where the important bit is in the string while (search._flags == 0) { //Assume that strings are treated as having an infinite number of null chars following them if (search._bit / 8 >= value.Length) { path.Add(search._leftId); search = trie[search._leftId]; continue; } // _leftId corresponds to bit=0, _rightId corresponds to bit=1 if (CheckBit(value, search._bit)) { path.Add(search._rightId); search = trie[search._rightId]; } else { path.Add(search._leftId); search = trie[search._leftId]; } } string searchVal = table->GetStringEntry(search._stringId); //Can't add duplicate strings if (searchVal == value) { throw new ArgumentException("Duplicate string"); } bool mismatch = false; int minLength = Math.Min(searchVal.Length, value.Length); short bit = 0; //Locate mismatching character between the two strings for (short i = 0; i < minLength; i++) { if (value[i] != searchVal[i]) { mismatch = true; bit = (short)(8 * i); break; } } bool right; //If a char was different one string does not contain the other if (mismatch) { //Find where the bits differed int cmpint = value[bit / 8] ^ searchVal[bit / 8]; bit += clz8[cmpint]; //If the bit is 1 the string being added takes the left fork right = CheckBit(value, bit); if (head->_numEntries == 1) { trie[1] = new SYMBMaskEntry(1, -1, -1, -1, id, 1); trie[2] = new SYMBMaskEntry(0, bit, right ? 0 : 1, right ? 1 : 0, -1, -1); head->_numEntries = 3; head->_rootId = 2; return; } //If the mismatch bit is lower than the first mismatch bit the new branch will be the root of the tree if (bit < trie[path[0]]._bit) { trie[head->_numEntries++] = new SYMBMaskEntry(1, -1, -1, -1, id, head->_numEntries / 2); if (right) { trie[head->_numEntries++] = new SYMBMaskEntry(0, bit, path[0], head->_numEntries - 2, -1, -1); } else { trie[head->_numEntries++] = new SYMBMaskEntry(0, bit, head->_numEntries - 2, path[0], -1, -1); } head->_rootId = head->_numEntries - 1; return; } //Locate where the branch needs to be inserted for (int i = 1; i < path.Count; i++) { if (trie[path[i]]._bit > bit || trie[path[i]]._flags == 1) { //Add leaf trie[head->_numEntries++] = new SYMBMaskEntry(1, -1, -1, -1, id, head->_numEntries / 2); //Remap previous branch to point to new branch if (trie[path[i - 1]]._leftId == path[i]) { trie[path[i - 1]]._leftId = head->_numEntries; } else { trie[path[i - 1]]._rightId = head->_numEntries; } //Create new branch if (right) { trie[head->_numEntries++] = new SYMBMaskEntry(0, bit, path[i], head->_numEntries - 2, -1, -1); } else { trie[head->_numEntries++] = new SYMBMaskEntry(0, bit, head->_numEntries - 2, path[i], -1, -1); } return; } } //This should never happen throw new Exception("Error building tree, unexpected structure"); } //Since mismatch is false, one string is a substring of the other //The longer string is the one that takes the left branch right = value.Length > searchVal.Length; bit = (short)(minLength * 8); if (right) { //Find the first bit after the substring that's 1. Will always occur in the first 8 bits because 0x00 denotes string termination and thus isn't in value bit += clz8[value[bit / 8]]; //If path.Count == 1 the only value is a leaf if (path.Count == 1) { trie[1] = new SYMBMaskEntry(1, -1, -1, -1, id, 1); trie[2] = new SYMBMaskEntry(0, bit, 0, 1, -1, -1); head->_numEntries = 3; head->_rootId = 2; return; } //Update old branch, insert new branch and node, and quit. trie[path[path.Count-2]] is the last branch that was a comparison. trie[head->_numEntries++] = new SYMBMaskEntry(1, -1, -1, -1, id, head->_numEntries / 2); int trace = path.Count - 2; if (trie[path[trace]]._leftId == path[trace + 1]) { //Handling an extremely specific and annoying edge case while (trie[path[trace]]._bit > bit) { trace--; if (trace < 0) { //This node is actually the root of the tree trie[head->_numEntries++] = new SYMBMaskEntry(0, bit, path[0], head->_numEntries - 2, -1, -1); head->_rootId = head->_numEntries - 2; return; } } trie[path[trace]]._leftId = head->_numEntries; } else { trie[path[trace]]._rightId = head->_numEntries; } trie[head->_numEntries++] = new SYMBMaskEntry(0, bit, path[trace + 1], head->_numEntries - 2, -1, -1); return; } //Find first bit comparison that happens after the substring ends int index; for (index = 0; trie[path[index]]._flags == 0 && trie[path[index]]._bit <= bit; index++) { } //Find the first bit that's 1 and isn't already used in the trie int cmpVal = searchVal[bit / 8]; byte clzVal; bool test = trie[path[index]]._flags == 0; while (true) { clzVal = clz8[cmpVal]; if (clzVal == 8) { bit += 8; cmpVal = searchVal[bit / 8]; continue; } if (test && trie[path[index]]._bit <= bit + clzVal) { if (trie[path[index]]._bit == bit + clzVal) { cmpVal ^= (1 << 7) >> clzVal; } test = trie[path[++index]]._flags == 0; continue; } bit += clzVal; break; } //If the trie is a single leaf the new branch is the root of the trie if (head->_numEntries == 1) { trie[1] = new SYMBMaskEntry(1, -1, -1, -1, id, 1); trie[2] = new SYMBMaskEntry(0, bit, 1, 0, -1, -1); head->_numEntries = 3; head->_rootId = 2; return; } //Update old branch, insert new branch and node, and quit trie[head->_numEntries++] = new SYMBMaskEntry(1, -1, -1, -1, id, head->_numEntries / 2); if (trie[path[index - 1]]._leftId == path[index]) { trie[path[index - 1]]._leftId = head->_numEntries; } else { trie[path[index - 1]]._rightId = head->_numEntries; } trie[head->_numEntries++] = new SYMBMaskEntry(0, bit, head->_numEntries - 2, path[index], -1, -1); return; }