public void Init(StreamReader.IReader fileReader, UInt64 offset, ref VGM_Stream vgmStream, bool InitReader, UInt64 fileLength)
        {
            UInt64 start_offset;
            UInt64 i;

            int thpVersion = fileReader.Read_8Bits(offset + 0x06);

            /* fill in the vital statistics */
            start_offset = fileReader.Read_32bitsBE(offset + 0x28);

            // Get info from the first block
            UInt64 componentTypeOffset = offset + fileReader.Read_32bitsBE(offset + 0x20);
            uint   numComponents       = fileReader.Read_32bitsBE(componentTypeOffset);
            UInt64 componentDataOffset = componentTypeOffset + 0x14;

            componentTypeOffset += 4;

            for (i = 0; i < numComponents; i++)
            {
                if (fileReader.Read_8Bits(componentTypeOffset + i) == 1) // audio block
                {
                    uint channel_count = fileReader.Read_32bitsBE(componentDataOffset);

                    /* build the VGMSTREAM */
                    VGM_Utils.allocate_vgmStream(ref vgmStream, (int)channel_count, false);

                    vgmStream.vgmChannelCount = (int)channel_count;
                    vgmStream.vgmSampleRate   = (int)fileReader.Read_32bitsBE(componentDataOffset + 4);
                    vgmStream.vgmTotalSamples = (int)fileReader.Read_32bitsBE(componentDataOffset + 8);
                    break;
                }
                else
                {
                    if (thpVersion == 0x10)
                    {
                        componentDataOffset += 0x0c;
                    }
                    else
                    {
                        componentDataOffset += 0x08;
                    }
                }
            }

            vgmStream.vgmTHPNextFrameSize = fileReader.Read_32bitsBE(offset + 0x18);
            vgmStream.vgmDecoder          = new DSP_Decoder();
            vgmStream.vgmLayout           = new Blocked();
            vgmStream.vgmLayoutType       = VGM_Layout_Type.THP_Blocked;

            if (InitReader)
            {
                for (i = 0; i < 2; i++)
                {
                    vgmStream.vgmChannel[i].fReader = (StreamReader.IReader)Activator.CreateInstance(fileReader.GetType());;
                    vgmStream.vgmChannel[i].fReader.Open(fileReader.GetFilename());
                    vgmStream.vgmChannel[i].fReader.SetSessionID(fileReader.GetSessionID());
                }
                BlockedFnts.THP_Block_Update(offset + start_offset, ref vgmStream);
            }
        }
Exemple #2
0
        public UInt64 IsFormat(StreamReader.IReader fileReader, UInt64 offset, UInt64 length)
        {
            if (fileReader.Read_32bitsBE(offset + 0) != 0x5253544D) /* "RSTM" */
            {
                return(0);
            }

            if ((fileReader.Read_32bitsBE(offset + 0x04) != 0xFEFF0100) && (fileReader.Read_32bitsBE(offset + 0x04) != 0xFEFF0001))
            {
                return(0);
            }

            /* get head offset, check */
            UInt64 head_offset = fileReader.Read_16bitsBE(offset + 0x0C);

            if (head_offset == 0x10)
            {
                m_Description = "Nintendo RSTM Header v1";
            }
            else
            {
                m_Description = "Nintendo RSTM Header v2";
            }

            UInt64 rl_head_offset = head_offset + offset;

            if (fileReader.Read_32bitsBE(head_offset + offset) != 0x48454144) /* "HEAD" */
            {
                return(0);
            }

            /* check type details */
            byte codec_number  = fileReader.Read_8Bits((head_offset == 0x10) ? rl_head_offset + 0x8 : rl_head_offset + 0x20);
            bool loop_flag     = (fileReader.Read_8Bits((head_offset == 0x10) ? rl_head_offset + 0x19 : rl_head_offset + 0x21) != 0);
            uint channel_count = fileReader.Read_8Bits((head_offset == 0x10) ? rl_head_offset + 0xa : rl_head_offset + 0x22);
            uint sample_rate   = fileReader.Read_16bitsBE((head_offset == 0x10) ? rl_head_offset + 0xc : rl_head_offset + 0x24);

            if (!VGM_Utils.CheckChannels(channel_count))
            {
                return(0);
            }

            if (!VGM_Utils.CheckSampleRate(sample_rate))
            {
                return(0);
            }

            if (codec_number > 2)
            {
                return(0);
            }

            return(fileReader.Read_32bitsBE(offset + 0x08));
        }
        public void Init(StreamReader.IReader fileReader, UInt64 offset, ref VGM_Stream vgmStream, bool InitReader, UInt64 fileLength)
        {
            Int16[] coef = new Int16[16] {
                0x04ab, -0x0313, 0x0789, -0x0121, 0x09a2, -0x051b, 0x0c90, -0x053f, 0x084d, -0x055c, 0x0982, -0x0209, 0x0af6, -0x0506, 0x0be6, -0x040b
            };
            int channel_count = fileReader.Read_8Bits(offset + 0x16);

            /* build the VGMSTREAM */
            VGM_Utils.allocate_vgmStream(ref vgmStream, channel_count, false);

            vgmStream.vgmDecoder = new DSP_Decoder();
            vgmStream.vgmLayout  = new Interleave();

            vgmStream.vgmChannelCount = channel_count;
            vgmStream.vgmLoopFlag     = false;
            vgmStream.vgmTotalSamples = (int)(fileReader.Read_32bits(offset + 0x42) / 8 * 14) / channel_count;
            vgmStream.vgmSampleRate   = fileReader.Read_16bits(offset + 0x18);

            if (channel_count == 1)
            {
                vgmStream.vgmLayout = new NoLayout();
            }
            else
            {
                vgmStream.vgmLayout     = new Interleave();
                vgmStream.vgmLayoutType = VGM_Layout_Type.Interleave_With_Shortblock;
            }

            vgmStream.vgmInterleaveBlockSize = 0x08;

            int i, j;

            for (j = 0; j < vgmStream.vgmChannelCount; j++)
            {
                for (i = 0; i < 16; i++)
                {
                    vgmStream.vgmChannel[j].adpcm_coef[i] = coef[i];
                }
            }

            UInt64 start_offset = offset + 0x46;

            if (InitReader)
            {
                for (i = 0; i < channel_count; i++)
                {
                    vgmStream.vgmChannel[i].fReader = (StreamReader.IReader)Activator.CreateInstance(fileReader.GetType());;
                    vgmStream.vgmChannel[i].fReader.Open(fileReader.GetFilename());
                    vgmStream.vgmChannel[i].fReader.SetSessionID(fileReader.GetSessionID());

                    vgmStream.vgmChannel[i].currentOffset = start_offset + (UInt64)(i * vgmStream.vgmInterleaveBlockSize);
                }
            }
        }
Exemple #4
0
        public UInt64 IsFormat(StreamReader.IReader fileReader, UInt64 offset, UInt64 length)
        {
            if (fileReader.Read_16bitsBE(offset) != 0x8000)
            {
                return(0);
            }

            // Check encoding type ...
            if (fileReader.Read_8Bits(offset + 4) != 3)
            {
                return(0);
            }

            // Check Frame Size ...
            if (fileReader.Read_8Bits(offset + 5) != 18)
            {
                return(0);
            }

            // Check Bit per Samples ...
            if (fileReader.Read_8Bits(offset + 6) != 4)
            {
                return(0);
            }

            // Check for channel count & sample rate
            uint sampleRate   = fileReader.Read_32bitsBE(offset + 0x08);
            uint channelCount = fileReader.Read_8Bits(offset + 0x07);

            if (((channelCount <= 0) || (channelCount > 8)) ||
                ((sampleRate < 11025) || (sampleRate > 48000)))
            {
                return(0);
            }

            // Search for the (c)CRI signature ...
            UInt64 criOffset = fileReader.Read_16bitsBE(offset + 2);

            if ((fileReader.Read_32bitsBE(offset + criOffset - 4) != 0x00002863) &&
                (fileReader.Read_32bitsBE(offset + criOffset) != 0x29435249))
            {
                return(0);
            }

            // Find file length ...
            UInt32 totalSamples = fileReader.Read_32bitsBE(offset + 0x0C);
            UInt64 searchOffset = offset + (totalSamples * channelCount) / 32 * 18 + criOffset + 4;

            while (fileReader.Read_16bitsBE(searchOffset) != 0x8001)
            {
                searchOffset++;
                if (!fileReader.CanRead())
                {
                    break;
                }
            }

            UInt64 fileLength = (searchOffset - offset) + fileReader.Read_16bitsBE(searchOffset + 2) + 4;

            return(fileLength);
        }
Exemple #5
0
        public void Init(StreamReader.IReader fileReader, UInt64 offset, ref VGM_Stream vgmStream, bool InitReader, UInt64 fileLength)
        {
            bool   loop_flag         = false;
            UInt32 loop_start_sample = 0;
            UInt32 loop_start_offset;
            UInt32 loop_end_sample = 0;
            UInt32 loop_end_offset;

            Int16 coef1 = 0;
            Int16 coef2 = 0;

            int i;

            int channel_count = fileReader.Read_8Bits(offset + 0x07);

            /* check version signature, read loop info */
            UInt16 version_signature = fileReader.Read_16bitsBE(offset + 0x12);
            UInt64 criOffset         = (UInt64)fileReader.Read_16bitsBE(offset + 2) + 4;

            /* encryption */
            if (version_signature == 0x0408)
            {
                //if (find_key(streamFile, &xor_start, &xor_mult, &xor_add))
                //{
                //    coding_type = coding_CRI_ADX_enc;
                //    version_signature = 0x0400;
                //}
            }
            if (version_signature == 0x0300)
            {      /* type 03 */
                if (criOffset - 6 >= 0x2c)
                {  /* enough space for loop info? */
                    loop_flag         = (fileReader.Read_32bitsBE(offset + 0x18) != 0);
                    loop_start_sample = (fileReader.Read_32bitsBE(offset + 0x1c));
                    loop_start_offset = fileReader.Read_32bitsBE(offset + 0x20);
                    loop_end_sample   = fileReader.Read_32bitsBE(offset + 0x24);
                    loop_end_offset   = fileReader.Read_32bitsBE(offset + 0x28);
                }
            }
            else if (version_signature == 0x0400)
            {
                UInt32 ainf_info_length = 0;

                if (fileReader.Read_32bitsBE(offset + 0x24) == 0x41494E46) /* AINF Header */
                {
                    ainf_info_length = fileReader.Read_32bitsBE(offset + 0x28);
                }

                if (criOffset - ainf_info_length - 6 >= 0x38)
                {   /* enough space for loop info? */
                    loop_flag         = (fileReader.Read_32bitsBE(offset + 0x24) != 0);
                    loop_start_sample = (fileReader.Read_32bitsBE(offset + 0x28));
                    loop_start_offset = fileReader.Read_32bitsBE(offset + 0x2C);
                    loop_end_sample   = fileReader.Read_32bitsBE(offset + 0x30);
                    loop_end_offset   = fileReader.Read_32bitsBE(offset + 0x34);
                }
            }
            else if (version_signature == 0x0500)
            {   /* found in some SFD : Buggy Heat, appears to have no loop */
            }

            /* build the VGMSTREAM */
            VGM_Utils.allocate_vgmStream(ref vgmStream, channel_count, loop_flag);

            vgmStream.vgmChannelCount = channel_count;
            vgmStream.vgmSampleRate   = (int)fileReader.Read_32bitsBE(offset + 0x08);
            vgmStream.vgmTotalSamples = (int)fileReader.Read_32bitsBE(offset + 0x0C);

            vgmStream.vgmDecoder = new ADX_Decoder();

            if (channel_count == 1)
            {
                vgmStream.vgmLayout = new NoLayout();
            }
            else
            {
                vgmStream.vgmLayout = new Interleave();
            }

            vgmStream.vgmLoopFlag = loop_flag;

            if (loop_flag)
            {
                vgmStream.vgmLoopStartSample = (int)loop_start_sample;
                vgmStream.vgmLoopEndSample   = (int)loop_end_sample;
            }

            /* high-pass cutoff frequency, always 500 that I've seen */
            UInt16 cutoff = fileReader.Read_16bitsBE(offset + 0x10);

            /* calculate filter coefficients */
            {
                double x, y, z, a, b, c;

                x = cutoff;
                y = vgmStream.vgmSampleRate;
                z = Math.Cos(2.0 * Math.PI * x / y);

                a = Math.Sqrt(2) - z;
                b = Math.Sqrt(2) - 1.0;
                c = (a - Math.Sqrt((a + b) * (a - b))) / b;

                coef1 = (Int16)Math.Floor(c * 8192);
                coef2 = (Int16)Math.Floor(c * c * -4096);
            }

            vgmStream.vgmInterleaveBlockSize = 18;

            if (InitReader)
            {
                for (i = 0; i < channel_count; i++)
                {
                    vgmStream.vgmChannel[i].fReader = (StreamReader.IReader)Activator.CreateInstance(fileReader.GetType());;
                    vgmStream.vgmChannel[i].fReader.Open(fileReader.GetFilename());
                    vgmStream.vgmChannel[i].fReader.SetSessionID(fileReader.GetSessionID());

                    vgmStream.vgmChannel[i].adpcm_coef[0] = coef1;
                    vgmStream.vgmChannel[i].adpcm_coef[1] = coef2;
                    vgmStream.vgmChannel[i].startOffset   = vgmStream.vgmChannel[i].currentOffset = offset + criOffset + (UInt64)(vgmStream.vgmInterleaveBlockSize * i);
                }
            }
        }
Exemple #6
0
        public void Init(StreamReader.IReader fileReader, UInt64 offset, ref VGM_Stream vgmStream, bool InitReader, UInt64 fileLength)
        {
            UInt64 head_offset = fileReader.Read_16bitsBE(offset + 0x0C);

            if (head_offset == 0x10)
            {
                m_Description = "Nintendo RSTM Header v1";
            }
            else
            {
                m_Description = "Nintendo RSTM Header v2";
            }

            UInt64 rl_head_offset = head_offset + offset;

            bool loop_flag = (fileReader.Read_8Bits((head_offset == 0x10) ? rl_head_offset + 0x19 : rl_head_offset + 0x21) != 0);
            bool isDSP     = false;

            int codec_number  = fileReader.Read_8Bits((head_offset == 0x10) ? rl_head_offset + 0x8 : rl_head_offset + 0x20);
            int channel_count = fileReader.Read_8Bits((head_offset == 0x10) ? rl_head_offset + 0xa : rl_head_offset + 0x22);

            /* build the VGMSTREAM */
            VGM_Utils.allocate_vgmStream(ref vgmStream, channel_count, loop_flag);

            switch (codec_number)
            {
            case 0:
                vgmStream.vgmDecoder = new PCM8_Decoder();
                break;

            case 1:
                vgmStream.vgmDecoder     = new PCM16_Decoder();
                vgmStream.vgmDecoderType = VGM_Decoder_Type.PCM16BITSBE;
                break;

            case 2:
                isDSP = true;
                vgmStream.vgmDecoder = new DSP_Decoder();
                break;
            }

            vgmStream.vgmChannelCount = channel_count;
            vgmStream.vgmLoopFlag     = loop_flag;
            vgmStream.vgmTotalSamples = (int)fileReader.Read_32bitsBE((head_offset == 0x10) ? rl_head_offset + 0x14 : rl_head_offset + 0x2c);
            vgmStream.vgmSampleRate   = fileReader.Read_16bitsBE((head_offset == 0x10) ? rl_head_offset + 0xC : rl_head_offset + 0x24);

            if (loop_flag)
            {
                vgmStream.vgmLoopStartSample = (int)fileReader.Read_32bitsBE((head_offset == 0x10) ? rl_head_offset + 0x10 : rl_head_offset + 0x28);
                vgmStream.vgmLoopEndSample   = vgmStream.vgmTotalSamples;
            }

            if (channel_count == 1)
            {
                vgmStream.vgmLayout = new NoLayout();
            }
            else
            {
                vgmStream.vgmLayout     = new Interleave();
                vgmStream.vgmLayoutType = VGM_Layout_Type.Interleave_With_Shortblock;
            }

            vgmStream.vgmInterleaveBlockSize      = (int)fileReader.Read_32bitsBE((head_offset == 0x10) ? rl_head_offset + 0x20 : rl_head_offset + 0x38);
            vgmStream.vgmInterleaveShortBlockSize = (int)fileReader.Read_32bitsBE((head_offset == 0x10) ? rl_head_offset + 0x30 : rl_head_offset + 0x48);

            if (isDSP)
            {
                int i, j;
                int coef_spacing = ((head_offset == 0x10) ? 0x30 : 0x38);

                UInt64 coef_offset1 = fileReader.Read_32bitsBE(rl_head_offset + 0x1C);
                UInt64 coef_offset2 = fileReader.Read_32bitsBE(rl_head_offset + 0x10 + coef_offset1);
                UInt64 coef_offset  = ((head_offset == 0x10) ? 0x38 : coef_offset2 + 0x10);

                for (j = 0; j < vgmStream.vgmChannelCount; j++)
                {
                    for (i = 0; i < 16; i++)
                    {
                        vgmStream.vgmChannel[j].adpcm_coef[i] = (Int16)fileReader.Read_16bitsBE(rl_head_offset + coef_offset + (UInt64)(j * coef_spacing + i * 2));
                    }
                }
            }

            UInt64 start_offset = offset + fileReader.Read_32bitsBE((head_offset == 0x10) ? rl_head_offset + 0x18 : rl_head_offset + 0x30);

            if (InitReader)
            {
                for (int i = 0; i < channel_count; i++)
                {
                    vgmStream.vgmChannel[i].fReader = (StreamReader.IReader)Activator.CreateInstance(fileReader.GetType());;
                    vgmStream.vgmChannel[i].fReader.Open(fileReader.GetFilename());
                    vgmStream.vgmChannel[i].fReader.SetSessionID(fileReader.GetSessionID());

                    vgmStream.vgmChannel[i].currentOffset = start_offset + (UInt64)(i * vgmStream.vgmInterleaveBlockSize);
                }
            }
        }
        public void Init(StreamReader.IReader fileReader, UInt64 offset, ref VGM_Stream vgmStream, bool InitReader, UInt64 fileLength)
        {
            int i;

            char vagID         = (char)fileReader.Read_8Bits(offset + 0x03);
            uint fileVagLength = fileReader.Read_32bitsBE(offset + 0x0c);

            int  channel_count = 1;
            uint interleave    = 0;

            UInt64 loopStart = 0;
            UInt64 loopEnd   = 0;

            bool loop_flag = false;

            switch (vagID)
            {
            case 'i':
                channel_count = 2;
                break;

            case 'V':
                if (fileReader.Read_32bitsBE(offset + 0x20) == 0x53746572)     // vag Stereo
                {
                    channel_count = 2;
                }
                break;

            case 'p':
                if (fileReader.Read_32bitsBE(offset + 0x24) == 0x56414778)
                {
                    loop_flag     = false;
                    channel_count = 2;
                }
                else
                {
                    if (fileReader.Read_32bitsBE(offset + 0x04) <= 0x00000004)
                    {
                        loop_flag     = (fileReader.Read_32bitsBE(offset + 0x14) != 0);
                        channel_count = 1;
                    }
                    else
                    {
                        /* Search for loop in VAG */
                        uint   vagfileLength = fileReader.Read_32bitsBE(offset + 0x0c);
                        UInt64 readOffset    = offset + 0x20;

                        do
                        {
                            readOffset += 0x10;

                            // Loop Start ...
                            if (fileReader.Read_8Bits(readOffset + 0x01) == 0x06)
                            {
                                if (loopStart == 0)
                                {
                                    loopStart = readOffset;
                                }
                            }

                            // Loop End ...
                            if (fileReader.Read_8Bits(readOffset + 0x01) == 0x03)
                            {
                                if (loopEnd == 0)
                                {
                                    loopEnd = readOffset;
                                }
                            }

                            // Loop from end to beginning ...
                            if ((fileReader.Read_8Bits(readOffset + 0x01) == 0x01))
                            {
                                // Check if we have the eof tag after the loop point ...
                                // if so we don't loop, if not present, we loop from end to start ...
                                byte[] vagBuffer = fileReader.Read(readOffset + 0x10, 0x10);
                                if ((vagBuffer[0] != 0) && (vagBuffer[0] != 0x0c))
                                {
                                    if ((vagBuffer[0] == 0x00) && (vagBuffer[0] == 0x07))
                                    {
                                        loopStart = 0x40;
                                        loopEnd   = readOffset;
                                    }
                                }
                            }
                        } while (readOffset < offset + 0x20 + vagfileLength);
                        loop_flag = (loopEnd != 0);
                    }
                }
                break;

            default:
                break;
            }

            /* build the VGMSTREAM */
            VGM_Utils.allocate_vgmStream(ref vgmStream, channel_count, loop_flag);

            /* fill in the vital statistics */
            UInt64 start_offset = offset + 0x30;

            vgmStream.vgmChannelCount = channel_count;
            vgmStream.vgmSampleRate   = (int)fileReader.Read_32bitsBE(offset + 0x10);
            vgmStream.vgmTotalSamples = (int)(fileReader.Read_32bits(offset + 0x04) * 28 / 16);

            vgmStream.vgmLayout = new NoLayout();

            switch (vagID)
            {
            case 'i':     // VAGi
                vgmStream.vgmLayout       = new Interleave();
                vgmStream.vgmTotalSamples = (int)fileReader.Read_32bitsBE(0x0C) / 16 * 28;
                interleave   = fileReader.Read_32bitsBE(offset + 0x08);
                start_offset = offset + 0x800;
                break;

            case 'p':              // VAGp
                interleave = 0x10; // used for loop calc

                if (fileReader.Read_32bitsBE(offset + 0x04) == 0x00000004)
                {
                    vgmStream.vgmChannelCount = 2;
                    vgmStream.vgmTotalSamples = (int)fileReader.Read_32bitsBE(offset + 0x0C);

                    if (loop_flag)
                    {
                        vgmStream.vgmLoopStartSample = (int)fileReader.Read_32bitsBE(offset + 0x14);
                        vgmStream.vgmLoopEndSample   = (int)fileReader.Read_32bitsBE(offset + 0x18);
                    }

                    start_offset = offset + 0x80;

                    vgmStream.vgmLayout = new Interleave();

                    // Double VAG Header @ 0x0000 & 0x1000
                    if (fileReader.Read_32bitsBE(offset + 0) == fileReader.Read_32bitsBE(offset + 0x1000))
                    {
                        vgmStream.vgmTotalSamples = (int)fileReader.Read_32bitsBE(offset + 0x0C) / 16 * 28;
                        interleave   = 0x1000;
                        start_offset = offset + 0;
                    }
                }
                else
                {
                    if (fileReader.Read_32bitsBE(offset + 0x24) == 0x56414778)
                    {
                        if (fileReader.Read_16bitsBE(offset + fileReader.Read_32bitsBE(offset + 0x0C) + 0x10) != 0x0007)
                        {
                            interleave = 0x8000;
                        }

                        vgmStream.vgmLayout       = new Interleave();
                        vgmStream.vgmTotalSamples = (int)fileReader.Read_32bitsBE(offset + 0x0C) / 16 * 14;
                    }
                    else
                    {
                        vgmStream.vgmTotalSamples = (int)fileReader.Read_32bitsBE(offset + 0x0C) / 16 * 28;
                        start_offset = offset + 0x30;
                    }
                }
                break;

            case 'V':     // pGAV
                vgmStream.vgmLayout = new Interleave();
                interleave          = 0x2000;

                // Jak X hack ...
                if (fileReader.Read_32bitsBE(offset + 0x1000) == 0x56414770)
                {
                    interleave = 0x1000;
                }

                vgmStream.vgmSampleRate   = (int)fileReader.Read_32bits(offset + 0x10);
                vgmStream.vgmTotalSamples = (int)fileReader.Read_32bits(offset + 0x0C) / 16 * 14;
                start_offset = offset + 0;
                break;
            }

            vgmStream.vgmDecoder  = new PSX_Decoder();
            vgmStream.vgmLoopFlag = loop_flag;

            if (loop_flag)
            {
                vgmStream.vgmLoopStartSample = (int)(fileReader.Read_32bits(offset + 0x08) * 28 / 16 / vgmStream.vgmChannelCount);
                vgmStream.vgmLoopEndSample   = (int)(fileReader.Read_32bits(offset + 0x04) * 28 / 16);
            }

            vgmStream.vgmInterleaveBlockSize = (int)interleave;

            if (InitReader)
            {
                for (i = 0; i < channel_count; i++)
                {
                    vgmStream.vgmChannel[i].fReader = (StreamReader.IReader)Activator.CreateInstance(fileReader.GetType());;
                    vgmStream.vgmChannel[i].fReader.Open(fileReader.GetFilename());
                    vgmStream.vgmChannel[i].startOffset = vgmStream.vgmChannel[i].currentOffset = start_offset + (UInt64)(vgmStream.vgmInterleaveBlockSize * i);
                }
            }
        }