void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket packet)
{
    if (is_ooc_muted) {
        sendServerMessage("You are OOC muted, and cannot speak.");
        return;
    }

    ooc_name = dezalgo(argv[0]).replace(QRegExp("\\[|\\]|\\{|\\}|\\#|\\$|\\%|\\&"), ""); // no fucky wucky shit here
    if (ooc_name.isEmpty() || ooc_name == ConfigManager::serverName()) // impersonation & empty name protection
        return;

    if (ooc_name.length() > 30) {
        sendServerMessage("Your name is too long! Please limit it to under 30 characters.");
        return;
    }

    if (is_logging_in) {
        loginAttempt(argv[1]);
        return;
    }

    QString message = dezalgo(argv[1]);
    if (message.length() == 0 || message.length() > ConfigManager::maxCharacters())
        return;
    AOPacket final_packet("CT", {ooc_name, message, "0"});
    if(message.at(0) == '/') {
        QStringList cmd_argv = message.split(" ", QString::SplitBehavior::SkipEmptyParts);
        QString command = cmd_argv[0].trimmed().toLower();
        command = command.right(command.length() - 1);
        cmd_argv.removeFirst();
        int cmd_argc = cmd_argv.length();

        handleCommand(command, cmd_argc, cmd_argv);
        area->logCmd(current_char, ipid, command, cmd_argv);
        return;
    }
    else {
        server->broadcast(final_packet, current_area);
    }
    area->log(current_char, ipid, final_packet);
}


void AOClient::pktHpBar(AreaData* area, int argc, QStringList argv, AOPacket packet)
{
    if (is_wtce_blocked) {
        sendServerMessage("You are blocked from using the judge controls.");
        return;
    }
    int l_newValue = argv.at(1).toInt();

    if (argv[0] == "1") {
        area->changeHP(AreaData::Side::DEFENCE, l_newValue);
    }
    else if (argv[0] == "2") {
        area->changeHP(AreaData::Side::PROSECUTOR, l_newValue);
    }

    server->broadcast(AOPacket("HP", {"1", QString::number(area->defHP())}), area->index());
    server->broadcast(AOPacket("HP", {"2", QString::number(area->proHP())}), area->index());

    updateJudgeLog(area, this, "updated the penalties");
}


void AOClient::pktEditEvidence(AreaData* area, int argc, QStringList argv, AOPacket packet)
{
    if (!checkEvidenceAccess(area))
        return;
    bool is_int = false;
    int idx = argv[0].toInt(&is_int);
    AreaData::Evidence evi = {argv[1], argv[2], argv[3]};
    if (is_int && idx < area->evidence().size() && idx >= 0) {
        area->replaceEvidence(idx, evi);
    }
    sendEvidenceList(area);
}


void CSoundFile::HandlePatternTransitionEvents()
{
	// MPT sequence override
	if(m_PlayState.m_nSeqOverride != ORDERINDEX_INVALID && m_PlayState.m_nSeqOverride < Order().size())
	{
		if(m_SongFlags[SONG_PATTERNLOOP])
		{
			m_PlayState.m_nPattern = Order()[m_PlayState.m_nSeqOverride];
		}
		m_PlayState.m_nCurrentOrder = m_PlayState.m_nSeqOverride;
		m_PlayState.m_nSeqOverride = ORDERINDEX_INVALID;
	}

	// Channel mutes
	for (CHANNELINDEX chan = 0; chan < GetNumChannels(); chan++)
	{
		if (m_bChannelMuteTogglePending[chan])
		{
			if(GetpModDoc())
			{
				GetpModDoc()->MuteChannel(chan, !GetpModDoc()->IsChannelMuted(chan));
			}
			m_bChannelMuteTogglePending[chan] = false;
		}
	}
}


void CSoundFile::ExtendedChannelEffect(ModChannel *pChn, uint32 param)
{
	// S9x and X9x commands (S3M/XM/IT only)
	if(!m_SongFlags[SONG_FIRSTTICK]) return;
	switch(param & 0x0F)
	{
	// S90: Surround Off
	case 0x00:	pChn->dwFlags.reset(CHN_SURROUND);	break;
	// S91: Surround On
	case 0x01:	pChn->dwFlags.set(CHN_SURROUND); pChn->nPan = 128; break;

	////////////////////////////////////////////////////////////
	// ModPlug Extensions
	// S98: Reverb Off
	case 0x08:
		pChn->dwFlags.reset(CHN_REVERB);
		pChn->dwFlags.set(CHN_NOREVERB);
		break;
	// S99: Reverb On
	case 0x09:
		pChn->dwFlags.reset(CHN_NOREVERB);
		pChn->dwFlags.set(CHN_REVERB);
		break;
	// S9A: 2-Channels surround mode
	case 0x0A:
		m_SongFlags.reset(SONG_SURROUNDPAN);
		break;
	// S9B: 4-Channels surround mode
	case 0x0B:
		m_SongFlags.set(SONG_SURROUNDPAN);
		break;
	// S9C: IT Filter Mode
	case 0x0C:
		m_SongFlags.reset(SONG_MPTFILTERMODE);
		break;
	// S9D: MPT Filter Mode
	case 0x0D:
		m_SongFlags.set(SONG_MPTFILTERMODE);
		break;
	// S9E: Go forward
	case 0x0E:
		pChn->dwFlags.reset(CHN_PINGPONGFLAG);
		break;
	// S9F: Go backward (and set playback position to the end if sample just started)
	case 0x0F:
		if(pChn->position.IsZero() && pChn->nLength && (pChn->rowCommand.IsNote() || !pChn->dwFlags[CHN_LOOP]))
		{
			pChn->position.Set(pChn->nLength - 1, SamplePosition::fractMax);
		}
		pChn->dwFlags.set(CHN_PINGPONGFLAG);
		break;
	}
}


uint32 CSoundFile::CalculateXParam(PATTERNINDEX pat, ROWINDEX row, CHANNELINDEX chn, bool *isExtended) const
{
	if(isExtended != nullptr) *isExtended = false;
	ROWINDEX maxCommands = 4;
	const ModCommand *m = Patterns[pat].GetpModCommand(row, chn);
	uint32 val = m->param;

	switch(m->command)
	{
	case CMD_OFFSET:
		// 24 bit command
		maxCommands = 2;
		break;
	case CMD_TEMPO:
	case CMD_PATTERNBREAK:
	case CMD_POSITIONJUMP:
		// 16 bit command
		maxCommands = 1;
		break;
	default:
		return val;
	}

	const bool xmTempoFix = m->command == CMD_TEMPO && GetType() == MOD_TYPE_XM;
	ROWINDEX numRows = std::min(Patterns[pat].GetNumRows() - row - 1, maxCommands);
	while(numRows > 0)
	{
		m += Patterns[pat].GetNumChannels();
		if(m->command != CMD_XPARAM)
		{
			break;
		}
		if(xmTempoFix && val < 256)
		{
			// With XM, 0x20 is the lowest tempo. Anything below changes ticks per row.
			val -= 0x20;
		}
		val = (val << 8) | m->param;
		numRows--;
		if(isExtended != nullptr) *isExtended = true;
	}
	return val;
}


void CSoundFile::PortamentoFineMPT(ModChannel* pChn, int param)
{
	//Behavior: Divides portamento change between ticks/row. For example
	//if Ticks/row == 6, and param == +-6, portamento goes up/down by one tuning-dependent
	//fine step every tick.

	if(m_PlayState.m_nTickCount == 0)
		pChn->nOldFinePortaUpDown = 0;

	const int tickParam = static_cast<int>((m_PlayState.m_nTickCount + 1.0) * param / m_PlayState.m_nMusicSpeed);
	pChn->m_PortamentoFineSteps += (param >= 0) ? tickParam - pChn->nOldFinePortaUpDown : tickParam + pChn->nOldFinePortaUpDown;
	if(m_PlayState.m_nTickCount + 1 == m_PlayState.m_nMusicSpeed)
		pChn->nOldFinePortaUpDown = static_cast<int8>(mpt::abs(param));
	else
		pChn->nOldFinePortaUpDown = static_cast<int8>(mpt::abs(tickParam));

	pChn->m_CalculateFreq = true;
}


ROWINDEX CSoundFile::PatternBreak(PlayState &state, CHANNELINDEX chn, uint8 param) const
{
	if(param >= 64 && (GetType() & MOD_TYPE_S3M))
	{
		// ST3 ignores invalid pattern breaks.
		return ROWINDEX_INVALID;
	}

	state.m_nNextPatStartRow = 0; // FT2 E60 bug

	return static_cast<ROWINDEX>(CalculateXParam(state.m_nPattern, state.m_nRow, chn));
}


void CSoundFile::ExtendedS3MCommands(CHANNELINDEX nChn, ModCommand::PARAM param)
{
	ModChannel *pChn = &m_PlayState.Chn[nChn];
	uint8 command = param & 0xF0;
	param &= 0x0F;
	switch(command)
	{
	// S0x: Set Filter
	// S1x: Set Glissando Control
	case 0x10:	pChn->dwFlags.set(CHN_GLISSANDO, param != 0); break;
	// S2x: Set FineTune
	case 0x20:	if(!m_SongFlags[SONG_FIRSTTICK]) break;
				if(GetType() != MOD_TYPE_669)
				{
					pChn->nC5Speed = S3MFineTuneTable[param];
					pChn->nFineTune = MOD2XMFineTune(param);
					if (pChn->nPeriod) pChn->nPeriod = GetPeriodFromNote(pChn->nNote, pChn->nFineTune, pChn->nC5Speed);
				} else if(pChn->pModSample != nullptr)
				{
					pChn->nC5Speed = pChn->pModSample->nC5Speed + param * 80;
				}
				break;
	// S3x: Set Vibrato Waveform
	case 0x30:	if(GetType() == MOD_TYPE_S3M)
				{
					pChn->nVibratoType = param & 0x03;
				} else
				{
					// IT compatibility: Ignore waveform types > 3
					if(m_playBehaviour[kITVibratoTremoloPanbrello])
						pChn->nVibratoType = (param < 0x04) ? param : 0;
					else
						pChn->nVibratoType = param & 0x07;
				}
				break;
	// S4x: Set Tremolo Waveform
	case 0x40:	if(GetType() == MOD_TYPE_S3M)
				{
					pChn->nTremoloType = param & 0x03;
				} else
				{
					// IT compatibility: Ignore waveform types > 3
					if(m_playBehaviour[kITVibratoTremoloPanbrello])
						pChn->nTremoloType = (param < 0x04) ? param : 0;
					else
						pChn->nTremoloType = param & 0x07;
				}
				break;
	// S5x: Set Panbrello Waveform
	case 0x50:
		// IT compatibility: Ignore waveform types > 3
				if(m_playBehaviour[kITVibratoTremoloPanbrello])
				{
					pChn->nPanbrelloType = (param < 0x04) ? param : 0;
					pChn->nPanbrelloPos = 0;
				} else
				{
					pChn->nPanbrelloType = param & 0x07;
				}
				break;
	// S6x: Pattern Delay for x frames
	case 0x60:
				if(m_SongFlags[SONG_FIRSTTICK] && m_PlayState.m_nTickCount == 0)
				{
					// Tick delays are added up.
					// Scream Tracker 3 does actually not support this command.
					// We'll use the same behaviour as for Impulse Tracker, as we can assume that
					// most S3Ms that make use of this command were made with Impulse Tracker.
					// MPT added this command to the XM format through the X6x effect, so we will use
					// the same behaviour here as well.
					// Test cases: PatternDelays.it, PatternDelays.s3m, PatternDelays.xm
					m_PlayState.m_nFrameDelay += param;
				}
				break;
	// S7x: Envelope Control / Instrument Control
	case 0x70:	if(!m_SongFlags[SONG_FIRSTTICK]) break;
				switch(param)
				{
				case 0:
				case 1:
				case 2:
					{
						ModChannel *bkp = &m_PlayState.Chn[m_nChannels];
						for (CHANNELINDEX i=m_nChannels; i<MAX_CHANNELS; i++, bkp++)
						{
							if (bkp->nMasterChn == nChn+1)
							{
								if (param == 1)
								{
									KeyOff(bkp);
								} else if (param == 2)
								{
									bkp->dwFlags.set(CHN_NOTEFADE);
								} else
								{
									bkp->dwFlags.set(CHN_NOTEFADE);
									bkp->nFadeOutVol = 0;
								}
#ifndef NO_PLUGINS
								const ModInstrument *pIns = bkp->pModInstrument;
								IMixPlugin *pPlugin;
								if(pIns != nullptr && pIns->nMixPlug && (pPlugin = m_MixPlugins[pIns->nMixPlug - 1].pMixPlugin) != nullptr)
								{
									pPlugin->MidiCommand(GetBestMidiChannel(nChn), pIns->nMidiProgram, pIns->wMidiBank, bkp->nNote + NOTE_MAX_SPECIAL, 0, nChn);
								}
#endif // NO_PLUGINS
							}
						}
					}
					break;
				case 3:		pChn->nNNA = NNA_NOTECUT; break;
				case 4:		pChn->nNNA = NNA_CONTINUE; break;
				case 5:		pChn->nNNA = NNA_NOTEOFF; break;
				case 6:		pChn->nNNA = NNA_NOTEFADE; break;
				case 7:		pChn->VolEnv.flags.reset(ENV_ENABLED); break;
				case 8:		pChn->VolEnv.flags.set(ENV_ENABLED); break;
				case 9:		pChn->PanEnv.flags.reset(ENV_ENABLED); break;
				case 10:	pChn->PanEnv.flags.set(ENV_ENABLED); break;
				case 11:	pChn->PitchEnv.flags.reset(ENV_ENABLED); break;
				case 12:	pChn->PitchEnv.flags.set(ENV_ENABLED); break;
				case 13:	// S7D: Enable pitch envelope, force to play as pitch envelope
				case 14:	// S7E: Enable pitch envelope, force to play as filter envelope
					if(GetType() == MOD_TYPE_MPT)
					{
						pChn->PitchEnv.flags.set(ENV_ENABLED);
						pChn->PitchEnv.flags.set(ENV_FILTER, param != 13);
					}
					break;
				}
				break;
	// S8x: Set 4-bit Panning
	case 0x80:
		if(m_SongFlags[SONG_FIRSTTICK])
		{
			Panning(pChn, param, Pan4bit);
		}
		break;
	// S9x: Sound Control
	case 0x90:	ExtendedChannelEffect(pChn, param); break;
	// SAx: Set 64k Offset
	case 0xA0:	if(m_SongFlags[SONG_FIRSTTICK])
				{
					pChn->nOldHiOffset = static_cast<uint8>(param);
					if (!m_playBehaviour[kITHighOffsetNoRetrig] && pChn->rowCommand.IsNote())
					{
						SmpLength pos = param << 16;
						if (pos < pChn->nLength) pChn->position.SetInt(pos);
					}
				}
				break;
	// SBx: Pattern Loop
	// SCx: Note Cut
	case 0xC0:
		if(param == 0)
		{
			//IT compatibility 22. SC0 == SC1
			if(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))
				param = 1;
			// ST3 doesn't cut notes with SC0
			else if(GetType() == MOD_TYPE_S3M)
				return;
		}
		// S3M/IT compatibility: Note Cut really cuts notes and does not just mute them (so that following volume commands could restore the sample)
		// Test case: scx.it
		NoteCut(nChn, param, m_playBehaviour[kITSCxStopsSample] || GetType() == MOD_TYPE_S3M);
		break;
	// SDx: Note Delay
	// SEx: Pattern Delay for x rows
	// SFx: S3M: Not used, IT: Set Active Midi Macro
	case 0xF0:
		if(GetType() != MOD_TYPE_S3M)
		{
			pChn->nActiveMacro = static_cast<uint8>(param);
		}
		break;
	}
}


PLUGINDEX CSoundFile::GetBestPlugin(CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const
{
	if (nChn >= MAX_CHANNELS)		//Check valid channel number
	{
		return 0;
	}

	//Define search source order
	PLUGINDEX nPlugin = 0;
	switch (priority)
	{
		case ChannelOnly:
			nPlugin = GetChannelPlugin(nChn, respectMutes);
			break;
		case InstrumentOnly:
			nPlugin  = GetActiveInstrumentPlugin(nChn, respectMutes);
			break;
		case PrioritiseInstrument:
			nPlugin  = GetActiveInstrumentPlugin(nChn, respectMutes);
			if ((!nPlugin) || (nPlugin > MAX_MIXPLUGINS))
			{
				nPlugin = GetChannelPlugin(nChn, respectMutes);
			}
			break;
		case PrioritiseChannel:
			nPlugin  = GetChannelPlugin(nChn, respectMutes);
			if ((!nPlugin) || (nPlugin > MAX_MIXPLUGINS))
			{
				nPlugin = GetActiveInstrumentPlugin(nChn, respectMutes);
			}
			break;
	}

	return nPlugin; // 0 Means no plugin found.
}


PLUGINDEX CSoundFile::GetActiveInstrumentPlugin(CHANNELINDEX nChn, PluginMutePriority respectMutes) const
{
	// Unlike channel settings, pModInstrument is copied from the original chan to the NNA chan,
	// so we don't need to worry about finding the master chan.

	PLUGINDEX plug = 0;
	if(m_PlayState.Chn[nChn].pModInstrument != nullptr)
	{
		if(respectMutes == RespectMutes && m_PlayState.Chn[nChn].pModSample && m_PlayState.Chn[nChn].pModSample->uFlags[CHN_MUTE])
		{
			plug = 0;
		} else
		{
			plug = m_PlayState.Chn[nChn].pModInstrument->nMixPlug;
		}
	}
	return plug;
}


static struct page *page_trans_compound_anon(struct page *page)
{
	if (PageTransCompound(page)) {
		struct page *head = compound_trans_head(page);
		/*
		 * head may actually be splitted and freed from under
		 * us but it's ok here.
		 */
		if (PageAnon(head))
			return head;
	}
	return NULL;
}


static ssize_t pages_to_scan_store(struct kobject *kobj,
				   struct kobj_attribute *attr,
				   const char *buf, size_t count)
{
	int err;
	unsigned long nr_pages;

	err = strict_strtoul(buf, 10, &nr_pages);
	if (err || nr_pages > UINT_MAX)
		return -EINVAL;

	ksm_thread_pages_to_scan = nr_pages;

	return count;
}


struct page *ksm_does_need_to_copy(struct page *page,
			struct vm_area_struct *vma, unsigned long address)
{
	struct page *new_page;

	new_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, address);
	if (new_page) {
		copy_user_highpage(new_page, page, address, vma);

		SetPageDirty(new_page);
		__SetPageUptodate(new_page);
		SetPageSwapBacked(new_page);
		__set_page_locked(new_page);

		if (page_evictable(new_page, vma))
			lru_cache_add_lru(new_page, LRU_ACTIVE_ANON);
		else
			add_page_to_unevictable_list(new_page);
	}

	return new_page;
}


int __ksm_enter(struct mm_struct *mm)
{
	struct mm_slot *mm_slot;
	int needs_wakeup;

	mm_slot = alloc_mm_slot();
	if (!mm_slot)
		return -ENOMEM;

	/* Check ksm_run too?  Would need tighter locking */
	needs_wakeup = list_empty(&ksm_mm_head.mm_list);

	spin_lock(&ksm_mmlist_lock);
	insert_to_mm_slots_hash(mm, mm_slot);
	/*
	 * Insert just behind the scanning cursor, to let the area settle
	 * down a little; when fork is followed by immediate exec, we don't
	 * want ksmd to waste time setting up and tearing down an rmap_list.
	 */
	list_add_tail(&mm_slot->mm_list, &ksm_scan.mm_slot->mm_list);
	spin_unlock(&ksm_mmlist_lock);

	set_bit(MMF_VM_MERGEABLE, &mm->flags);
	atomic_inc(&mm->mm_count);

	if (needs_wakeup)
		wake_up_interruptible(&ksm_thread_wait);

	return 0;
}


int ksm_madvise(struct vm_area_struct *vma, unsigned long start,
		unsigned long end, int advice, unsigned long *vm_flags)
{
	struct mm_struct *mm = vma->vm_mm;
	int err;

	switch (advice) {
	case MADV_MERGEABLE:
		/*
		 * Be somewhat over-protective for now!
		 */
		if (*vm_flags & (VM_MERGEABLE | VM_SHARED  | VM_MAYSHARE   |
				 VM_PFNMAP    | VM_IO      | VM_DONTEXPAND |
				 VM_RESERVED  | VM_HUGETLB | VM_INSERTPAGE |
				 VM_NONLINEAR | VM_MIXEDMAP | VM_SAO))
			return 0;		/* just ignore the advice */

		if (!test_bit(MMF_VM_MERGEABLE, &mm->flags)) {
			err = __ksm_enter(mm);
			if (err)
				return err;
		}

		*vm_flags |= VM_MERGEABLE;
		break;

	case MADV_UNMERGEABLE:
		if (!(*vm_flags & VM_MERGEABLE))
			return 0;		/* just ignore the advice */

		if (vma->anon_vma) {
			err = unmerge_ksm_pages(vma, start, end);
			if (err)
				return err;
		}

		*vm_flags &= ~VM_MERGEABLE;
		break;
	}

	return 0;
}


static struct stable_node *stable_tree_insert(struct page *kpage)
{
	struct rb_node **new = &root_stable_tree.rb_node;
	struct rb_node *parent = NULL;
	struct stable_node *stable_node;

	while (*new) {
		struct page *tree_page;
		int ret;

		cond_resched();
		stable_node = rb_entry(*new, struct stable_node, node);
		tree_page = get_ksm_page(stable_node);
		if (!tree_page)
			return NULL;

		ret = memcmp_pages(kpage, tree_page);
		put_page(tree_page);

		parent = *new;
		if (ret < 0)
			new = &parent->rb_left;
		else if (ret > 0)
			new = &parent->rb_right;
		else {
			/*
			 * It is not a bug that stable_tree_search() didn't
			 * find this node: because at that time our page was
			 * not yet write-protected, so may have changed since.
			 */
			return NULL;
		}
	}

	stable_node = alloc_stable_node();
	if (!stable_node)
		return NULL;

	rb_link_node(&stable_node->node, parent, new);
	rb_insert_color(&stable_node->node, &root_stable_tree);

	INIT_HLIST_HEAD(&stable_node->hlist);

	stable_node->kpfn = page_to_pfn(kpage);
	set_page_stable_node(kpage, stable_node);

	return stable_node;
}


int rmap_walk_ksm(struct page *page, int (*rmap_one)(struct page *,
		  struct vm_area_struct *, unsigned long, void *), void *arg)
{
	struct stable_node *stable_node;
	struct hlist_node *hlist;
	struct rmap_item *rmap_item;
	int ret = SWAP_AGAIN;
	int search_new_forks = 0;

	VM_BUG_ON(!PageKsm(page));
	VM_BUG_ON(!PageLocked(page));

	stable_node = page_stable_node(page);
	if (!stable_node)
		return ret;
again:
	hlist_for_each_entry(rmap_item, hlist, &stable_node->hlist, hlist) {
		struct anon_vma *anon_vma = rmap_item->anon_vma;
		struct anon_vma_chain *vmac;
		struct vm_area_struct *vma;

		anon_vma_lock(anon_vma);
		list_for_each_entry(vmac, &anon_vma->head, same_anon_vma) {
			vma = vmac->vma;
			if (rmap_item->address < vma->vm_start ||
			    rmap_item->address >= vma->vm_end)
				continue;
			/*
			 * Initially we examine only the vma which covers this
			 * rmap_item; but later, if there is still work to do,
			 * we examine covering vmas in other mms: in case they
			 * were forked from the original since ksmd passed.
			 */
			if ((rmap_item->mm == vma->vm_mm) == search_new_forks)
				continue;

			ret = rmap_one(page, vma, rmap_item->address, arg);
			if (ret != SWAP_AGAIN) {
				anon_vma_unlock(anon_vma);
				goto out;
			}
		}
		anon_vma_unlock(anon_vma);
	}
	if (!search_new_forks++)
		goto again;
out:
	return ret;
}


inline static const int16_t *get_line(const int16_t *ptr, uintptr_t offs, uintptr_t size)
{
    return offs < size ? ptr + offs : zero_line;
}


static void coeff_blur121(double *coeff, int n)
{
    double prev = coeff[1];
    for (int i = 0; i <= n; ++i) {
        double res = (prev + 2 * coeff[i] + coeff[i + 1]) / 4;
        prev = coeff[i];
        coeff[i] = res;
    }
}


void ass_blur1234_vert_c(int16_t *dst, const int16_t *src,
                         uintptr_t src_width, uintptr_t src_height,
                         const int16_t *param)
{
    uintptr_t dst_height = src_height + 8;
    uintptr_t step = STRIPE_WIDTH * src_height;

    for (uintptr_t x = 0; x < src_width; x += STRIPE_WIDTH) {
        uintptr_t offs = 0;
        for (uintptr_t y = 0; y < dst_height; ++y) {
            const int16_t *p4 = get_line(src, offs - 8 * STRIPE_WIDTH, step);
            const int16_t *p3 = get_line(src, offs - 7 * STRIPE_WIDTH, step);
            const int16_t *p2 = get_line(src, offs - 6 * STRIPE_WIDTH, step);
            const int16_t *p1 = get_line(src, offs - 5 * STRIPE_WIDTH, step);
            const int16_t *z0 = get_line(src, offs - 4 * STRIPE_WIDTH, step);
            const int16_t *n1 = get_line(src, offs - 3 * STRIPE_WIDTH, step);
            const int16_t *n2 = get_line(src, offs - 2 * STRIPE_WIDTH, step);
            const int16_t *n3 = get_line(src, offs - 1 * STRIPE_WIDTH, step);
            const int16_t *n4 = get_line(src, offs - 0 * STRIPE_WIDTH, step);
            for (int k = 0; k < STRIPE_WIDTH; ++k)
                dst[k] = blur_func(p4[k], p3[k], p2[k], p1[k], z0[k],
                                   n1[k], n2[k], n3[k], n4[k], param);
            dst += STRIPE_WIDTH;
            offs += STRIPE_WIDTH;
        }
        src += step;
    }
}


static void coeff_filter(double *coeff, int n, const double kernel[4])
{
    double prev1 = coeff[1], prev2 = coeff[2], prev3 = coeff[3];
    for (int i = 0; i <= n; ++i) {
        double res = coeff[i + 0]  * kernel[0] +
            (prev1 + coeff[i + 1]) * kernel[1] +
            (prev2 + coeff[i + 2]) * kernel[2] +
            (prev3 + coeff[i + 3]) * kernel[3];
        prev3 = prev2;
        prev2 = prev1;
        prev1 = coeff[i];
        coeff[i] = res;
    }
}


EIGEN_STRONG_INLINE QInt32 operator*(const QInt16 a, const QInt32 b) {
  return QInt32(static_cast<int32_t>(a.value) * b.value);
}


EIGEN_STRONG_INLINE bool operator==(const QInt32 a, const QInt32 b) {
  return a.value == b.value;
}


EIGEN_STRONG_INLINE QInt32& operator/=(QInt32& a, const QInt32 b) {
  a.value /= b.value;
  return a;
}


EIGEN_STRONG_INLINE QInt32 operator*(const QInt32 a, const QInt16 b) {
  return QInt32(a.value * static_cast<int32_t>(b.value));
}


QInt32(const uint32_t v) : value(static_cast<int32_t>(v)) {}


EIGEN_STRONG_INLINE bool operator==(const QUInt16 a, const QUInt16 b) {
  return a.value == b.value;
}


EIGEN_STRONG_INLINE bool operator<(const QInt8 a, const QInt8 b) {
  return a.value < b.value;
}


EIGEN_STRONG_INLINE QInt32 operator-(const QInt8 a, const QInt8 b) {
  return QInt32(static_cast<int32_t>(a.value) - static_cast<int32_t>(b.value));
}


EIGEN_STRONG_INLINE QInt32 operator+(const QInt32 a, const QInt16 b) {
  return QInt32(a.value + static_cast<int32_t>(b.value));
}


EIGEN_STRONG_INLINE QInt32& operator+=(QInt32& a, const QInt32 b) {
  a.value += b.value;
  return a;
}


EIGEN_STRONG_INLINE std::ostream& operator<<(std::ostream& os, QUInt16 a) {
  os << static_cast<int>(a.value);
  return os;
}


	__releases(dentry->d_inode->i_lock)
{
	struct inode *inode = dentry->d_inode;
	__d_clear_type_and_inode(dentry);
	hlist_del_init(&dentry->d_u.d_alias);
	dentry_rcuwalk_invalidate(dentry);
	spin_unlock(&dentry->d_lock);
	spin_unlock(&inode->i_lock);
	if (!inode->i_nlink)
		fsnotify_inoderemove(inode);
	if (dentry->d_op && dentry->d_op->d_iput)
		dentry->d_op->d_iput(dentry, inode);
	else
		iput(inode);
}


struct dentry *d_hash_and_lookup(struct dentry *dir, struct qstr *name)
{
	/*
	 * Check for a fs-specific hash function. Note that we must
	 * calculate the standard hash first, as the d_op->d_hash()
	 * routine may choose to leave the hash value unchanged.
	 */
	name->hash = full_name_hash(name->name, name->len);
	if (dir->d_flags & DCACHE_OP_HASH) {
		int err = dir->d_op->d_hash(dir, name);
		if (unlikely(err < 0))
			return ERR_PTR(err);
	}
	return d_lookup(dir, name);
}


static void __init dcache_init(void)
{
	unsigned int loop;

	/*
	 * A constructor could be added for stable state like the lists,
	 * but it is probably not worth it because of the cache nature
	 * of the dcache.
	 */
	dentry_cache = KMEM_CACHE(dentry,
		SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|SLAB_MEM_SPREAD);

	/* Hash may have been set up in dcache_init_early */
	if (!hashdist)
		return;

	dentry_hashtable =
		alloc_large_system_hash("Dentry cache",
					sizeof(struct hlist_bl_head),
					dhash_entries,
					13,
					0,
					&d_hash_shift,
					&d_hash_mask,
					0,
					0);

	for (loop = 0; loop < (1U << d_hash_shift); loop++)
		INIT_HLIST_BL_HEAD(dentry_hashtable + loop);
}


int d_instantiate_no_diralias(struct dentry *entry, struct inode *inode)
{
	BUG_ON(!hlist_unhashed(&entry->d_u.d_alias));

	spin_lock(&inode->i_lock);
	if (S_ISDIR(inode->i_mode) && !hlist_empty(&inode->i_dentry)) {
		spin_unlock(&inode->i_lock);
		iput(inode);
		return -EBUSY;
	}
	__d_instantiate(entry, inode);
	spin_unlock(&inode->i_lock);
	security_d_instantiate(entry, inode);

	return 0;
}


static inline void __d_clear_type_and_inode(struct dentry *dentry)
{
	unsigned flags = READ_ONCE(dentry->d_flags);

	flags &= ~(DCACHE_ENTRY_TYPE | DCACHE_FALLTHRU);
	WRITE_ONCE(dentry->d_flags, flags);
	smp_wmb();
	dentry->d_inode = NULL;
}


static int __d_unalias(struct inode *inode,
		struct dentry *dentry, struct dentry *alias)
{
	struct mutex *m1 = NULL, *m2 = NULL;
	int ret = -ESTALE;

	/* If alias and dentry share a parent, then no extra locks required */
	if (alias->d_parent == dentry->d_parent)
		goto out_unalias;

	/* See lock_rename() */
	if (!mutex_trylock(&dentry->d_sb->s_vfs_rename_mutex))
		goto out_err;
	m1 = &dentry->d_sb->s_vfs_rename_mutex;
	if (!mutex_trylock(&alias->d_parent->d_inode->i_mutex))
		goto out_err;
	m2 = &alias->d_parent->d_inode->i_mutex;
out_unalias:
	__d_move(alias, dentry, false);
	ret = 0;
out_err:
	spin_unlock(&inode->i_lock);
	if (m2)
		mutex_unlock(m2);
	if (m1)
		mutex_unlock(m1);
	return ret;
}


GF_Err gf_isom_get_edit(GF_ISOFile *the_file, u32 trackNumber, u32 SegmentIndex, u64 *EditTime, u64 *SegmentDuration, u64 *MediaTime, GF_ISOEditType *EditMode)
{
	u32 i;
	u64 startTime;
	GF_TrackBox *trak;
	GF_EditListBox *elst;
	GF_EdtsEntry *ent;

	ent = NULL;
	trak = gf_isom_get_track_from_file(the_file, trackNumber);
	if (!trak) return GF_BAD_PARAM;

	if (!trak->editBox ||
	        !trak->editBox->editList ||
	        (SegmentIndex > gf_list_count(trak->editBox->editList->entryList)) ||
	        !SegmentIndex)
		return GF_BAD_PARAM;

	elst = trak->editBox->editList;
	startTime = 0;

	for (i = 0; i < SegmentIndex; i++) {
		ent = (GF_EdtsEntry*)gf_list_get(elst->entryList, i);
		if (i < SegmentIndex-1) startTime += ent->segmentDuration;
	}
	*EditTime = startTime;
	*SegmentDuration = ent->segmentDuration;
	if (ent->mediaTime < 0) {
		*MediaTime = 0;
		*EditMode = GF_ISOM_EDIT_EMPTY;
		return GF_OK;
	}
	if (ent->mediaRate == 0) {
		*MediaTime = ent->mediaTime;
		*EditMode = GF_ISOM_EDIT_DWELL;
		return GF_OK;
	}
	*MediaTime = ent->mediaTime;
	*EditMode = GF_ISOM_EDIT_NORMAL;
	return GF_OK;
}


Bool gf_isom_is_encrypted_entry(u32 entryType)
{
	switch (entryType) {
	case GF_ISOM_BOX_TYPE_ENCA:
	case GF_ISOM_BOX_TYPE_ENCV:
	case GF_ISOM_BOX_TYPE_ENCS:
		return GF_TRUE;
	default:
		return GF_FALSE;
	}
}


GF_DecoderConfig *gf_isom_get_decoder_config(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex)
{
	GF_TrackBox *trak;
	GF_ESD *esd;
	GF_Descriptor *decInfo;
	trak = gf_isom_get_track_from_file(the_file, trackNumber);
	if (!trak) return NULL;
	//get the ESD (possibly emulated)
	Media_GetESD(trak->Media, StreamDescriptionIndex, &esd, GF_FALSE);
	if (!esd) return NULL;
	decInfo = (GF_Descriptor *) esd->decoderConfig;
	esd->decoderConfig = NULL;
	gf_odf_desc_del((GF_Descriptor *) esd);
	return (GF_DecoderConfig *)decInfo;
}


GF_Err gf_isom_purge_track_reference(GF_ISOFile *the_file, u32 trackNumber)
{
	GF_TrackBox *trak;
	GF_TrackReferenceTypeBox *ref;
	u32 i=0;
	trak = gf_isom_get_track_from_file(the_file, trackNumber);
	if (!trak) return GF_BAD_PARAM;

	//no tref, nothing to remove
	if (!trak->References) return GF_OK;

	while ((ref = gf_list_enum(trak->References->child_boxes, &i))) {
		u32 k;
		if (!ref->reference_type) continue;

		for (k=0; k<ref->trackIDCount; k++) {
			u32 tk = gf_isom_get_track_by_id(the_file, ref->trackIDs[k]);
			if (!tk) {
				memmove(&ref->trackIDs[k], &ref->trackIDs[k+1], ref->trackIDCount-k-1);
				k--;
				ref->trackIDCount--;
			}
		}
		if (!ref->trackIDCount) {
			i--;
			gf_isom_box_del_parent(&trak->References->child_boxes, (GF_Box *) ref);
		}
	}
	if (!trak->References->child_boxes || !gf_list_count(trak->References->child_boxes)) {
		gf_isom_box_del_parent(&trak->child_boxes, (GF_Box *) trak->References);
		trak->References = NULL;
	}
	return GF_OK;
}


void InitSL_NULL(GF_SLConfig *slc)
{
	memset(slc, 0, sizeof(GF_SLConfig));
	slc->tag = GF_ODF_SLC_TAG;
	slc->predefined = 0x01;
}


const u32 *gf_isom_get_brands(GF_ISOFile *movie)
{
	if (!movie || !movie->brand) return NULL;
	return movie->brand->altBrand;
}


u32 gf_isom_get_sync_point_count(GF_ISOFile *the_file, u32 trackNumber)
{
	GF_TrackBox *trak;
	trak = gf_isom_get_track_from_file(the_file, trackNumber);
	if (!trak) return 0;
	if (trak->Media->information->sampleTable->SyncSample) {
		return trak->Media->information->sampleTable->SyncSample->nb_entries;
	}
	return 0;
}


Bool gf_isom_get_sample_sync(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber)
{
	GF_ISOSAPType is_rap;
	GF_Err e;
	GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber);
	if (!trak || !sampleNumber) return GF_FALSE;

	if (! trak->Media->information->sampleTable->SyncSample) return GF_TRUE;
#ifndef	GPAC_DISABLE_ISOM_FRAGMENTS
	if (sampleNumber<=trak->sample_count_at_seg_start) return GF_FALSE;
	sampleNumber -= trak->sample_count_at_seg_start;
#endif
	e = stbl_GetSampleRAP(trak->Media->information->sampleTable->SyncSample, sampleNumber, &is_rap, NULL, NULL);
	if (e) return GF_FALSE;
	return is_rap ? GF_TRUE : GF_FALSE;
}


GF_Err gf_isom_sample_set_dep_info(GF_ISOFile *file, u32 track, u32 sampleNumber, u32 isLeading, u32 dependsOn, u32 dependedOn, u32 redundant)
{
	GF_TrackBox *trak;

	trak = gf_isom_get_track_from_file(file, track);
	if (!trak) return GF_BAD_PARAM;

	return stbl_AddDependencyType(trak->Media->information->sampleTable, sampleNumber, isLeading, dependsOn, dependedOn, redundant);
}


GF_EXPORT
Bool gf_isom_is_JPEG2000(GF_ISOFile *mov)
{
	return (mov && mov->is_jp2) ? GF_TRUE : GF_FALSE;


GF_EXPORT
GF_Err gf_isom_get_color_info(GF_ISOFile *movie, u32 trackNumber, u32 StreamDescriptionIndex, u32 *colour_type, u16 *colour_primaries, u16 *transfer_characteristics, u16 *matrix_coefficients, Bool *full_range_flag)
{
	GF_TrackBox *trak;
	GF_VisualSampleEntryBox *entry;
	GF_SampleDescriptionBox *stsd;

	trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak) return GF_BAD_PARAM;

	stsd = trak->Media->information->sampleTable->SampleDescription;
	if (!stsd) return movie->LastError = GF_ISOM_INVALID_FILE;
	if (!StreamDescriptionIndex || StreamDescriptionIndex > gf_list_count(stsd->child_boxes)) return movie->LastError = GF_BAD_PARAM;

	entry = (GF_VisualSampleEntryBox *)gf_list_get(stsd->child_boxes, StreamDescriptionIndex - 1);
	//no support for generic sample entries (eg, no MPEG4 descriptor)
	if (entry == NULL) return GF_OK;

	//valid for MPEG visual, JPG and 3GPP H263
	if (entry->internal_type!=GF_ISOM_SAMPLE_ENTRY_VIDEO) {
		return GF_BAD_PARAM;
	}
	GF_ColourInformationBox *clr = (GF_ColourInformationBox *) gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_COLR);
	if (!clr) return GF_NOT_FOUND;

	if (colour_type) *colour_type = clr->colour_type;
	if (colour_primaries) *colour_primaries = clr->colour_primaries;
	if (transfer_characteristics) *transfer_characteristics = clr->transfer_characteristics;
	if (matrix_coefficients) *matrix_coefficients = clr->matrix_coefficients;
	if (full_range_flag) *full_range_flag = clr->full_range_flag;
	return GF_OK;


GF_Err gf_isom_clone_pssh(GF_ISOFile *output, GF_ISOFile *input, Bool in_moof) {
	GF_Box *a;
	u32 i;
	i = 0;

	while ((a = (GF_Box *)gf_list_enum(input->moov->child_boxes, &i))) {
		if (a->type == GF_ISOM_BOX_TYPE_PSSH) {
			GF_List **child_boxes = &output->moov->child_boxes;
#ifndef GPAC_DISABLE_ISOM_FRAGMENTS
			if (in_moof)
				child_boxes = &output->moof->child_boxes;
#endif

			GF_ProtectionSystemHeaderBox *pssh = (GF_ProtectionSystemHeaderBox *)gf_isom_box_new_parent(child_boxes, GF_ISOM_BOX_TYPE_PSSH);
			if (!pssh) return GF_OUT_OF_MEM;
			memmove(pssh->SystemID, ((GF_ProtectionSystemHeaderBox *)a)->SystemID, 16);
			if (((GF_ProtectionSystemHeaderBox *)a)->KIDs && ((GF_ProtectionSystemHeaderBox *)a)->KID_count > 0) {
				pssh->KID_count = ((GF_ProtectionSystemHeaderBox *)a)->KID_count;
				pssh->KIDs = (bin128 *)gf_malloc(pssh->KID_count*sizeof(bin128));
				if (!pssh->KIDs) return GF_OUT_OF_MEM;
				memmove(pssh->KIDs, ((GF_ProtectionSystemHeaderBox *)a)->KIDs, pssh->KID_count*sizeof(bin128));
			}
			pssh->private_data_size = ((GF_ProtectionSystemHeaderBox *)a)->private_data_size;
			pssh->private_data = (u8 *)gf_malloc(pssh->private_data_size*sizeof(char));
			if (!pssh->private_data) return GF_OUT_OF_MEM;
			memmove(pssh->private_data, ((GF_ProtectionSystemHeaderBox *)a)->private_data, pssh->private_data_size);
		}
	}
	return GF_OK;
}


GF_Err gf_isom_set_track_index(GF_ISOFile *movie, u32 trackNumber, u32 index, void (*track_num_changed)(void *udta, u32 old_track_num, u32 new_track_num), void *udta)
{
	u32 i, count;
	GF_List *tracks;
	u32 prev_index=0, prev_pos=0;
	GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak || !index) return GF_BAD_PARAM;
	trak->index = index;
	tracks = gf_list_new();
	count = gf_list_count(movie->moov->trackList);
	//sort tracks in new list
	for (i=0; i<count; i++) {
		GF_TrackBox *a_tk = gf_list_get(movie->moov->trackList, i);
		if (!a_tk->index) {
			gf_list_insert(tracks, a_tk, 0);
		} else if (a_tk->index < prev_index) {
			gf_list_insert(tracks, a_tk, prev_pos);
		} else {
			gf_list_add(tracks, a_tk);
		}
		prev_pos = gf_list_count(tracks) - 1;
		prev_index = a_tk->index;
	}
	if (gf_list_count(tracks) != count) {
		gf_list_del(tracks);
		return GF_OUT_OF_MEM;
	}
	if (track_num_changed) {
		for (i=0; i<count; i++) {
			GF_TrackBox *a_tk = gf_list_get(tracks, i);
			s32 old_pos = gf_list_find(movie->moov->trackList, a_tk);
			assert(old_pos>=0);
			if (old_pos != i)
				track_num_changed(udta, old_pos+1, i+1);
		}
	}
	gf_list_del(movie->moov->trackList);
	movie->moov->trackList = tracks;
	return GF_OK;
}


#else
GF_Err gf_isom_get_sample_cenc_info_internal(GF_TrackBox *trak, void *traf, GF_SampleEncryptionBox *senc, u32 sample_number, Bool *IsEncrypted, u8 *crypt_byte_block, u8 *skip_byte_block, const u8 **key_info, u32 *key_info_size)
#endif
{
	GF_SampleGroupBox *sample_group;
	u32 j, group_desc_index;
	GF_SampleGroupDescriptionBox *sgdesc;
	u32 i, count;
	u32 descIndex, chunkNum;
	u64 offset;
	u32 first_sample_in_entry, last_sample_in_entry;
	GF_CENCSampleEncryptionGroupEntry *entry;

	if (IsEncrypted) *IsEncrypted = GF_FALSE;
	if (crypt_byte_block) *crypt_byte_block = 0;
	if (skip_byte_block) *skip_byte_block = 0;
	if (key_info) *key_info = NULL;
	if (key_info_size) *key_info_size = 0;

	if (!trak) return GF_BAD_PARAM;

#ifdef	GPAC_DISABLE_ISOM_FRAGMENTS
	if (traf)
		return GF_NOT_SUPPORTED;
#else
	sample_number -= trak->sample_count_at_seg_start;
#endif

	if (trak->Media->information->sampleTable->SampleSize && trak->Media->information->sampleTable->SampleSize->sampleCount>=sample_number) {
		stbl_GetSampleInfos(trak->Media->information->sampleTable, sample_number, &offset, &chunkNum, &descIndex, NULL);
	} else {
		//this is dump mode of fragments, we haven't merged tables yet - use current stsd idx indicated in trak
		descIndex = trak->current_traf_stsd_idx;
		if (!descIndex) descIndex = 1;
	}

	gf_isom_cenc_get_default_info_internal(trak, descIndex, NULL, IsEncrypted, crypt_byte_block, skip_byte_block, key_info, key_info_size);

	sample_group = NULL;
	group_desc_index = 0;
	if (trak->Media->information->sampleTable->sampleGroups) {
		count = gf_list_count(trak->Media->information->sampleTable->sampleGroups);
		for (i=0; i<count; i++) {
			sample_group = (GF_SampleGroupBox*)gf_list_get(trak->Media->information->sampleTable->sampleGroups, i);
			if (sample_group->grouping_type ==  GF_ISOM_SAMPLE_GROUP_SEIG)
				break;
			sample_group = NULL;
		}
		if (sample_group) {
			first_sample_in_entry = 1;
			for (j=0; j<sample_group->entry_count; j++) {
				last_sample_in_entry = first_sample_in_entry + sample_group->sample_entries[j].sample_count - 1;
				if ((sample_number<first_sample_in_entry) || (sample_number>last_sample_in_entry)) {
					first_sample_in_entry = last_sample_in_entry+1;
					continue;
				}
				/*we found our sample*/
				group_desc_index = sample_group->sample_entries[j].group_description_index;
				break;
			}
		}
	}
#ifndef GPAC_DISABLE_ISOM_FRAGMENTS
	if (!group_desc_index && traf && traf->sampleGroups) {
		count = gf_list_count(traf->sampleGroups);
		for (i=0; i<count; i++) {
			group_desc_index = 0;
			sample_group = (GF_SampleGroupBox*)gf_list_get(traf->sampleGroups, i);
			if (sample_group->grouping_type ==  GF_ISOM_SAMPLE_GROUP_SEIG)
				break;
			sample_group = NULL;
		}
		if (sample_group) {
			first_sample_in_entry = 1;
			for (j=0; j<sample_group->entry_count; j++) {
				last_sample_in_entry = first_sample_in_entry + sample_group->sample_entries[j].sample_count - 1;
				if ((sample_number<first_sample_in_entry) || (sample_number>last_sample_in_entry)) {
					first_sample_in_entry = last_sample_in_entry+1;
					continue;
				}
				/*we found our sample*/
				group_desc_index = sample_group->sample_entries[j].group_description_index;
				break;
			}
		}
	}
#endif
	/*no sampleGroup info associated*/
	if (!group_desc_index) goto exit;

	sgdesc = NULL;

	if (group_desc_index<=0x10000) {
		for (j=0; j<gf_list_count(trak->Media->information->sampleTable->sampleGroupsDescription); j++) {
			sgdesc = (GF_SampleGroupDescriptionBox*)gf_list_get(trak->Media->information->sampleTable->sampleGroupsDescription, j);
			if (sgdesc->grouping_type==sample_group->grouping_type) break;
			sgdesc = NULL;
		}
	}
#ifndef GPAC_DISABLE_ISOM_FRAGMENTS
	else if (traf) {
		group_desc_index -= 0x10000;
		for (j=0; j<gf_list_count(traf->sampleGroupsDescription); j++) {
			sgdesc = (GF_SampleGroupDescriptionBox*)gf_list_get(traf->sampleGroupsDescription, j);
			if (sgdesc->grouping_type==sample_group->grouping_type) break;
			sgdesc = NULL;
		}
	}
#endif
	/*no sampleGroup description found for this group (invalid file)*/
	if (!sgdesc) return GF_ISOM_INVALID_FILE;

	entry = (GF_CENCSampleEncryptionGroupEntry *) gf_list_get(sgdesc->group_descriptions, group_desc_index - 1);
	if (!entry) return GF_ISOM_INVALID_FILE;

	if (IsEncrypted) *IsEncrypted = entry->IsProtected;
	if (crypt_byte_block) *crypt_byte_block = entry->crypt_byte_block;
	if (skip_byte_block) *skip_byte_block = entry->skip_byte_block;

	if (key_info) *key_info = entry->key_info;
	if (key_info_size) *key_info_size = entry->key_info_size;

exit:
	//in PIFF we may have default values if no TENC is present: 8 bytes for IV size
	if (( (senc && senc->piff_type==1) || (trak->moov && trak->moov->mov->is_smooth) ) && key_info && ! (*key_info) ) {
		if (!senc) {
			if (IsEncrypted) *IsEncrypted = GF_TRUE;
			if (key_info_size) *key_info_size = 8;
		} else {
			if (!senc->piff_type) {
				senc->piff_type = 2;
				senc->IV_size = 8;
			}
			assert(senc->IV_size);
			if (IsEncrypted) *IsEncrypted = GF_TRUE;
			if (key_info_size) *key_info_size = senc->IV_size;
		}
	}

	return GF_OK;


u32 gf_isom_segment_get_track_fragment_count(GF_ISOFile *file, u32 moof_index)
{
#ifndef GPAC_DISABLE_ISOM_FRAGMENTS
	GF_MovieFragmentBox *moof;
	if (!file) return 0;
	gf_list_count(file->TopBoxes);
	moof = gf_isom_get_moof(file, moof_index);
	return moof ? gf_list_count(moof->TrackList) : 0;
#endif
	return 0;
}


u32 gf_isom_get_edits_count(GF_ISOFile *the_file, u32 trackNumber)
{
	GF_TrackBox *trak;
	trak = gf_isom_get_track_from_file(the_file, trackNumber);
	if (!trak) return 0;

	if (!trak->editBox || !trak->editBox->editList) return 0;
	return gf_list_count(trak->editBox->editList->entryList);
}


void gf_hinter_track_get_payload_name(GF_RTPHinter *tkHinter, char *payloadName)
{
	char mediaName[30];
	gf_rtp_builder_get_payload_name(tkHinter->rtp_p, payloadName, mediaName);
}


GF_Err gf_isom_reset_alt_brands(GF_ISOFile *movie)
{
	return gf_isom_reset_alt_brands_ex(movie, GF_FALSE);
}


u32 gf_isom_has_time_offset(GF_ISOFile *the_file, u32 trackNumber)
{
	u32 i;
	GF_CompositionOffsetBox *ctts;
	GF_TrackBox *trak;
	trak = gf_isom_get_track_from_file(the_file, trackNumber);
	if (!trak || !trak->Media->information->sampleTable->CompositionOffset) return 0;

	//return true at the first offset found
	ctts = trak->Media->information->sampleTable->CompositionOffset;
	for (i=0; i<ctts->nb_entries; i++) {
		if (ctts->entries[i].decodingOffset && ctts->entries[i].sampleCount) return ctts->version ? 2 : 1;
	}
	return 0;
}


u32 gf_isom_get_user_data_count(GF_ISOFile *movie, u32 trackNumber, u32 UserDataType, bin128 UUID)
{
	GF_UserDataMap *map;
	GF_TrackBox *trak;
	GF_UserDataBox *udta;
	bin128 t;
	u32 i, count;

	if (!movie || !movie->moov) return 0;

	if (UserDataType == GF_ISOM_BOX_TYPE_UUID) UserDataType = 0;
	memset(t, 1, 16);

	if (trackNumber) {
		trak = gf_isom_get_track_from_file(movie, trackNumber);
		if (!trak) return 0;
		udta = trak->udta;
	} else {
		udta = movie->moov->udta;
	}
	if (!udta) return 0;

	i=0;
	while ((map = (GF_UserDataMap*)gf_list_enum(udta->recordList, &i))) {
		count = gf_list_count(map->boxes);

		if ((map->boxType == GF_ISOM_BOX_TYPE_UUID) && !memcmp(map->uuid, UUID, 16)) return count;
		else if (map->boxType == UserDataType) return count;
	}
	return 0;
}


u32 gf_isom_new_track_from_template(GF_ISOFile *movie, GF_ISOTrackID trakID, u32 MediaType, u32 TimeScale, u8 *tk_box, u32 tk_box_size, Bool udta_only)
{
	GF_Err e;
	u64 now;
	u8 isHint;
	GF_TrackBox *trak;
	GF_TrackHeaderBox *tkhd;
	GF_MediaBox *mdia;
	GF_UserDataBox *udta = NULL;

	e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE);
	if (e) {
		gf_isom_set_last_error(movie, e);
		return 0;
	}
	e = gf_isom_insert_moov(movie);
	if (e) return e;

	isHint = 0;
	//we're creating a hint track... it's the same, but mode HAS TO BE EDIT
	if (MediaType == GF_ISOM_MEDIA_HINT) {
//		if (movie->openMode != GF_ISOM_OPEN_EDIT) return 0;
		isHint = 1;
	}

	mdia = NULL;
	tkhd = NULL;
	trak = NULL;
	if (trakID) {
		//check if we are in ES_ID boundaries
		if (!isHint && (trakID > 0xFFFF)) {
			gf_isom_set_last_error(movie, GF_BAD_PARAM);
			return 0;
		}
		//here we should look for available IDs ...
		if (!RequestTrack(movie->moov, trakID)) return 0;
	} else {
		trakID = movie->moov->mvhd->nextTrackID;
		if (!trakID) trakID = 1;
		/*ESIDs are on 16 bits*/
		if (! isHint && (trakID > 0xFFFF)) trakID = 1;

		while (1) {
			if (RequestTrack(movie->moov, trakID)) break;
			trakID += 1;
			if (trakID == 0xFFFFFFFF) break;
		}
		if (trakID == 0xFFFFFFFF) {
			gf_isom_set_last_error(movie, GF_BAD_PARAM);
			return 0;
		}
		if (! isHint && (trakID > 0xFFFF)) {
			gf_isom_set_last_error(movie, GF_BAD_PARAM);
			return 0;
		}
	}

	if (tk_box) {
		GF_BitStream *bs = gf_bs_new(tk_box, tk_box_size, GF_BITSTREAM_READ);
		gf_bs_set_cookie(bs, GF_ISOM_BS_COOKIE_NO_LOGS|GF_ISOM_BS_COOKIE_CLONE_TRACK);

		e = gf_isom_box_parse_ex((GF_Box**)&trak, bs, GF_ISOM_BOX_TYPE_MOOV, GF_FALSE);
		gf_bs_del(bs);
		if (e) trak = NULL;
		else if (udta_only) {
			udta = trak->udta;
			trak->udta = NULL;
			gf_isom_box_del((GF_Box*)trak);
		} else {
			Bool tpl_ok = GF_TRUE;
			if (!trak->Header || !trak->Media || !trak->Media->handler || !trak->Media->mediaHeader || !trak->Media->information) tpl_ok = GF_FALSE;

			else {
				if (!MediaType) MediaType = trak->Media->handler->handlerType;
				e = NewMedia(&trak->Media, MediaType, TimeScale);
				if (e) tpl_ok = GF_FALSE;
			}
			if (!tpl_ok) {
				udta = trak->udta;
				trak->udta = NULL;
				gf_isom_box_del((GF_Box*)trak);
			}
		}
	}
	now = gf_isom_get_mp4time();
	if (!trak) {
		//OK, now create a track...
		trak = (GF_TrackBox *) gf_isom_box_new_parent(&movie->moov->child_boxes, GF_ISOM_BOX_TYPE_TRAK);
		if (!trak) {
			gf_isom_set_last_error(movie, GF_OUT_OF_MEM);
			return 0;
		}
		tkhd = (GF_TrackHeaderBox *) gf_isom_box_new_parent(&trak->child_boxes, GF_ISOM_BOX_TYPE_TKHD);
		if (!tkhd) {
			gf_isom_set_last_error(movie, GF_OUT_OF_MEM);
			return 0;
		}

		//OK, set up the media trak
		e = NewMedia(&mdia, MediaType, TimeScale);
		if (e) {
			gf_isom_box_del((GF_Box *)mdia);
			return 0;
		}
		assert(trak->child_boxes);
		gf_list_add(trak->child_boxes, mdia);

		//OK, add this media to our track
		mdia->mediaTrack = trak;

		e = trak_on_child_box((GF_Box*)trak, (GF_Box *) tkhd, GF_FALSE);
		if (e) goto err_exit;
		e = trak_on_child_box((GF_Box*)trak, (GF_Box *) mdia, GF_FALSE);
		if (e) goto err_exit;
		tkhd->trackID = trakID;

		if (gf_sys_is_test_mode() ) {
			tkhd->creationTime = 0;
			mdia->mediaHeader->creationTime = 0;
		} else {
			tkhd->creationTime = now;
			mdia->mediaHeader->creationTime = now;
		}

	} else {
		tkhd = trak->Header;
		tkhd->trackID = trakID;
		mdia = trak->Media;
		mdia->mediaTrack = trak;
		mdia->mediaHeader->timeScale = TimeScale;
		if (mdia->handler->handlerType != MediaType) {
			mdia->handler->handlerType = MediaType;
			tkhd->width = 0;
			tkhd->height = 0;
			tkhd->volume = 0;
		} else {
			MediaType = 0;
		}
		trak->Header->duration = 0;
		mdia->mediaHeader->duration = 0;

		if (!movie->moov->child_boxes) movie->moov->child_boxes = gf_list_new();
		gf_list_add(movie->moov->child_boxes, trak);
	}
	if (MediaType) {
		//some default properties for Audio, Visual or private tracks
		switch (MediaType) {
		case GF_ISOM_MEDIA_VISUAL:
		case GF_ISOM_MEDIA_AUXV:
		case GF_ISOM_MEDIA_PICT:
		case GF_ISOM_MEDIA_SCENE:
		case GF_ISOM_MEDIA_TEXT:
		case GF_ISOM_MEDIA_SUBT:
			/*320-240 pix in 16.16*/
			tkhd->width = 0x01400000;
			tkhd->height = 0x00F00000;
			break;
		case GF_ISOM_MEDIA_AUDIO:
			tkhd->volume = 0x0100;
			break;
		}
	}
	movie->last_created_track_id = tkhd->trackID;

	if (!movie->keep_utc && !gf_sys_is_test_mode() ) {
		tkhd->modificationTime = now;
	 	mdia->mediaHeader->modificationTime = now;
	}

	//OK, add our trak
	e = moov_on_child_box((GF_Box*)movie->moov, (GF_Box *)trak, GF_FALSE);
	if (e) goto err_exit;
	//set the new ID available
	if (trakID+1> movie->moov->mvhd->nextTrackID)
		movie->moov->mvhd->nextTrackID = trakID+1;

	trak->udta = udta;

	//and return our track number
	return gf_isom_get_track_by_id(movie, trakID);

err_exit:
	//tkhd is registered with track and will be destroyed there
	if (trak) gf_isom_box_del((GF_Box *)trak);
	if (mdia) gf_isom_box_del((GF_Box *)mdia);
	return 0;
}


GF_Err gf_isom_set_copyright(GF_ISOFile *movie, const char *threeCharCode, char *notice)
{
	GF_Err e;
	GF_CopyrightBox *ptr;
	GF_UserDataMap *map;
	u32 count, i;

	e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE);
	if (e) return e;

	if (!notice || !threeCharCode) return GF_BAD_PARAM;

	e = gf_isom_insert_moov(movie);
	if (e) return e;

	if (!movie->moov->udta) {
		e = moov_on_child_box((GF_Box*)movie->moov, gf_isom_box_new_parent(&movie->moov->child_boxes, GF_ISOM_BOX_TYPE_UDTA), GF_FALSE);
		if (e) return e;
	}
	map = udta_getEntry(movie->moov->udta, GF_ISOM_BOX_TYPE_CPRT, NULL);

	if (map) {
		//try to find one in our language...
		count = gf_list_count(map->boxes);
		for (i=0; i<count; i++) {
			ptr = (GF_CopyrightBox*)gf_list_get(map->boxes, i);
			if (!strcmp(threeCharCode, (const char *) ptr->packedLanguageCode)) {
				gf_free(ptr->notice);
				ptr->notice = (char*)gf_malloc(sizeof(char) * (strlen(notice) + 1));
				if (!ptr->notice) return GF_OUT_OF_MEM;
				strcpy(ptr->notice, notice);
				return GF_OK;
			}
		}
	}
	//nope, create one
	ptr = (GF_CopyrightBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_CPRT);
	if (!ptr) return GF_OUT_OF_MEM;

	memcpy(ptr->packedLanguageCode, threeCharCode, 4);
	ptr->notice = (char*)gf_malloc(sizeof(char) * (strlen(notice)+1));
	if (!ptr->notice) return GF_OUT_OF_MEM;
	strcpy(ptr->notice, notice);
	return udta_on_child_box((GF_Box *)movie->moov->udta, (GF_Box *) ptr, GF_FALSE);
}


GF_Err gf_isom_update_duration(GF_ISOFile *movie)
{
	u32 i;
	u64 maxDur;
	GF_TrackBox *trak;

	if (!movie || !movie->moov) return GF_BAD_PARAM;

	//if file was open in Write or Edit mode, recompute the duration
	//the duration of a movie is the MaxDuration of all the tracks...

	maxDur = 0;
	i=0;
	while ((trak = (GF_TrackBox *)gf_list_enum(movie->moov->trackList, &i))) {
		if( (movie->LastError = SetTrackDuration(trak))	) return movie->LastError;
		if (trak->Header && (trak->Header->duration > maxDur))
			maxDur = trak->Header->duration;
	}
	movie->moov->mvhd->duration = maxDur;

	return GF_OK;
}


GF_EXPORT
u64 gf_isom_get_smooth_next_tfdt(GF_ISOFile *the_file, u32 trackNumber)
{
#ifdef	GPAC_DISABLE_ISOM_FRAGMENTS
	return 0;
#else
	GF_TrackBox *trak;
	trak = gf_isom_get_track_from_file(the_file, trackNumber);
	if (!trak) return 0;
	return trak->dts_at_next_seg_start;
#endif


GF_Err gf_hinter_track_force_no_offsets(GF_RTPHinter *tkHinter)
{
	GF_Err e;
	if (!tkHinter) return GF_BAD_PARAM;
	e = gf_isom_rtp_set_time_offset(tkHinter->file, tkHinter->HintTrack, 1, 0);
	if (e) return e;
	return gf_isom_rtp_set_time_sequence_offset(tkHinter->file, tkHinter->HintTrack, 1, 0);
}


GF_Err gf_isom_set_track_switch_parameter(GF_ISOFile *movie, u32 trackNumber, u32 trackRefGroup, Bool is_switch_group, u32 *switchGroupID, u32 *criteriaList, u32 criteriaListCount)
{
	GF_TrackSelectionBox *tsel;
	GF_TrackBox *trak;
	GF_UserDataMap *map;
	GF_Err e;
	u32 alternateGroupID = 0;
	u32 next_switch_group_id = 0;

	trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak || !switchGroupID) return GF_BAD_PARAM;

	if (trackRefGroup) {
		GF_TrackBox *trak_ref = gf_isom_get_track_from_file(movie, trackRefGroup);
		if (trak_ref != trak) {
			if (!trak_ref || !trak_ref->Header->alternate_group) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Track %d has not an alternate group - skipping\n", trak_ref ? trak_ref->Header->trackID : 0));
				return GF_BAD_PARAM;
			}
			alternateGroupID = trak_ref->Header->alternate_group;
		} else {
			alternateGroupID = trak->Header->alternate_group;
		}
	}
	if (!alternateGroupID) {
		/*there is a function for this ....*/
		if (trak->Header->alternate_group) {
			GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Track %d has already an alternate group - skipping\n", trak->Header->trackID));
			return GF_BAD_PARAM;
		}
		alternateGroupID = gf_isom_get_next_alternate_group_id(movie);
	}

	if (is_switch_group) {
		u32 i=0;
		while (i< gf_isom_get_track_count(movie) ) {
			//locate first available ID
			GF_TrackBox *a_trak = gf_isom_get_track_from_file(movie, i+1);

			if (a_trak->udta) {
				u32 j, count;
				map = udta_getEntry(a_trak->udta, GF_ISOM_BOX_TYPE_TSEL, NULL);
				if (map) {
					count = gf_list_count(map->boxes);
					for (j=0; j<count; j++) {
						tsel = (GF_TrackSelectionBox*)gf_list_get(map->boxes, j);

						if (*switchGroupID) {
							if (tsel->switchGroup==next_switch_group_id) {
								if (a_trak->Header->alternate_group != alternateGroupID) return GF_BAD_PARAM;
							}
						} else {
							if (tsel->switchGroup && (tsel->switchGroup>=next_switch_group_id) )
								next_switch_group_id = tsel->switchGroup;
						}
					}
				}

			}
			i++;
		}
		if (! *switchGroupID) *switchGroupID = next_switch_group_id+1;
	}

	trak->Header->alternate_group = alternateGroupID;

	tsel = NULL;
	if (*switchGroupID) {
		if (!trak->udta) {
			e = trak_on_child_box((GF_Box*)trak, gf_isom_box_new_parent(&trak->child_boxes, GF_ISOM_BOX_TYPE_UDTA), GF_FALSE);
			if (e) return e;
		}

		map = udta_getEntry(trak->udta, GF_ISOM_BOX_TYPE_TSEL, NULL);

		/*locate tsel box with no switch group*/
		if (map)  {
			u32 j, count = gf_list_count(map->boxes);
			for (j=0; j<count; j++) {
				tsel = (GF_TrackSelectionBox*)gf_list_get(map->boxes, j);
				if (tsel->switchGroup == *switchGroupID) break;
				tsel = NULL;
			}
		}
		if (!tsel) {
			tsel = (GF_TrackSelectionBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_TSEL);
			if (!tsel) return GF_OUT_OF_MEM;
			e = udta_on_child_box((GF_Box *)trak->udta, (GF_Box *) tsel, GF_FALSE);
			if (e) return e;
		}

		tsel->switchGroup = *switchGroupID;
		tsel->attributeListCount = criteriaListCount;
		if (tsel->attributeList) gf_free(tsel->attributeList);
		tsel->attributeList = (u32*)gf_malloc(sizeof(u32)*criteriaListCount);
		if (!tsel->attributeList) return GF_OUT_OF_MEM;
		memcpy(tsel->attributeList, criteriaList, sizeof(u32)*criteriaListCount);
	}
	return GF_OK;
}


u64 gf_isom_get_track_duration(GF_ISOFile *movie, u32 trackNumber)
{
	GF_TrackBox *trak;
	trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak) return 0;

#ifndef GPAC_DISABLE_ISOM_WRITE
	/*in all modes except dump recompute duration in case headers are wrong*/
	if (movie->openMode != GF_ISOM_OPEN_READ_DUMP) {
		SetTrackDuration(trak);
	}
#endif
	return trak->Header->duration;
}


GF_Err gf_isom_set_media_language(GF_ISOFile *movie, u32 trackNumber, char *code)
{
	GF_Err e;
	GF_TrackBox *trak;

	trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak || !code) return GF_BAD_PARAM;
	e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE);
	if (e) return e;

	// Old language-storage processing
	// if the new code is on 3 chars, we use it
	// otherwise, we find the associated 3 chars code and use it
	if (strlen(code) == 3) {
		memcpy(trak->Media->mediaHeader->packedLanguage, code, sizeof(char)*3);
	} else {
		s32 lang_idx;
		const char *code_3cc;
		lang_idx = gf_lang_find(code);
		if (lang_idx == -1) {
			GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("The given code is not a valid one: %s, using 'und' as 3-letter code\n", code));
			code_3cc = "und";
		} else {
			code_3cc = gf_lang_get_3cc(lang_idx);
		}
		memcpy(trak->Media->mediaHeader->packedLanguage, code_3cc, sizeof(char)*3);
	}

	// New language-storage processing
	// change the code in the extended language box (if any)
	// otherwise add an extended language box only if the given code is not 3 chars
	{
		u32 i, count;
		GF_ExtendedLanguageBox *elng;
		elng = NULL;
		count = gf_list_count(trak->Media->child_boxes);
		for (i = 0; i < count; i++) {
			GF_Box *box = (GF_Box *)gf_list_get(trak->Media->child_boxes, i);
			if (box->type == GF_ISOM_BOX_TYPE_ELNG) {
				elng = (GF_ExtendedLanguageBox *)box;
				break;
			}
		}
		if (!elng && (strlen(code) > 3)) {
			elng = (GF_ExtendedLanguageBox *)gf_isom_box_new_parent(&trak->Media->child_boxes, GF_ISOM_BOX_TYPE_ELNG);
			if (!elng) return GF_OUT_OF_MEM;
		}
		if (elng) {
			if (elng->extended_language) {
				gf_free(elng->extended_language);
			}
			elng->extended_language = gf_strdup(code);
		}
	}
	if (!movie->keep_utc)
		trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time();
	return GF_OK;
}


u32 gf_isom_new_track(GF_ISOFile *movie, GF_ISOTrackID trakID, u32 MediaType, u32 TimeScale)
{
	return gf_isom_new_track_from_template(movie, trakID, MediaType, TimeScale, NULL, 0, GF_FALSE);
}


u32 gf_isom_get_max_sample_size(GF_ISOFile *the_file, u32 trackNumber)
{
	GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber);
	if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->SampleSize) return 0;

	return trak->Media->information->sampleTable->SampleSize->max_size;
}


GF_EXPORT
GF_Err gf_isom_get_track_switch_group_count(GF_ISOFile *movie, u32 trackNumber, u32 *alternateGroupID, u32 *nb_groups)
{
	GF_UserDataMap *map;
	GF_TrackBox *trak;

	trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak || !trak->Header) return GF_BAD_PARAM;
	*alternateGroupID = trak->Header->alternate_group;
	*nb_groups = 0;
	if (!trak->udta) return GF_OK;

	map = udta_getEntry(trak->udta, GF_ISOM_BOX_TYPE_TSEL, NULL);
	if (!map) return GF_OK;
	*nb_groups = gf_list_count(map->boxes);
	return GF_OK;


GF_Err gf_isom_set_dolby_vision_profile(GF_ISOFile* movie, u32 trackNumber, u32 StreamDescriptionIndex, u32 dv_profile)
{
	GF_Err e;
	GF_TrackBox* trak;
	GF_SampleEntryBox* entry;
	GF_SampleDescriptionBox* stsd;
	GF_DOVIConfigurationBox* dovi = NULL;
	e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE);
	if (e) return e;

	trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak) return GF_BAD_PARAM;

	stsd = trak->Media->information->sampleTable->SampleDescription;
	if (!stsd) return movie->LastError = GF_ISOM_INVALID_FILE;
	if (!StreamDescriptionIndex || StreamDescriptionIndex > gf_list_count(stsd->child_boxes)) {
		return movie->LastError = GF_BAD_PARAM;
	}
	entry = (GF_SampleEntryBox*)gf_list_get(stsd->child_boxes, StreamDescriptionIndex - 1);
	//no support for generic sample entries (eg, no MPEG4 descriptor)
	if (entry == NULL) return GF_BAD_PARAM;
	if (!movie->keep_utc)
		trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time();

	if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_VIDEO) return GF_OK;

	dovi = ((GF_MPEGVisualSampleEntryBox*)entry)->dovi_config;
	if (!dv_profile) {
		if (dovi) gf_isom_box_del((GF_Box*)dovi);
		((GF_MPEGVisualSampleEntryBox*)entry)->dovi_config = NULL;
		return GF_OK;
	}
	if (!dovi) {
		dovi = (GF_DOVIConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_DVCC);
		if (!dovi) return GF_OUT_OF_MEM;
		((GF_MPEGVisualSampleEntryBox*)entry)->dovi_config = dovi;
	}
	entry->type = GF_ISOM_BOX_TYPE_DVHE;
	dovi->DOVIConfig.dv_profile = dv_profile;
	return GF_OK;
}


void gf_isom_set_last_error(GF_ISOFile *movie, GF_Err error)
{
	if (!movie) {
		MP4_API_IO_Err = error;
	} else {
		movie->LastError = error;
	}
}


GF_EXPORT
GF_Err gf_isom_get_jp2_config(GF_ISOFile *movie, u32 trackNumber, u32 sampleDesc, u8 **out_dsi, u32 *out_size)
{
	GF_TrackBox *trak;
	GF_MPEGVisualSampleEntryBox *entry;
	GF_BitStream *bs;

	trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->SampleDescription) return GF_ISOM_INVALID_FILE;
	entry = (GF_MPEGVisualSampleEntryBox *) gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, sampleDesc-1);
	if (!entry || !entry->jp2h) return GF_BAD_PARAM;
	if (!entry->jp2h->ihdr) return GF_ISOM_INVALID_FILE;

	bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
	gf_isom_box_array_write((GF_Box*)entry->jp2h, entry->jp2h->child_boxes, bs);
	gf_bs_get_content(bs, out_dsi, out_size);
	gf_bs_del(bs);
	return GF_OK;


s32 gf_isom_get_reference_count(GF_ISOFile *movie, u32 trackNumber, u32 referenceType)
{
	GF_TrackBox *trak;
	GF_TrackReferenceTypeBox *dpnd;
	trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak) return -1;
	if (!trak->References) return 0;
	if (movie->openMode == GF_ISOM_OPEN_WRITE) {
		movie->LastError = GF_ISOM_INVALID_MODE;
		return -1;
	}

	dpnd = NULL;
	if ( (movie->LastError = Track_FindRef(trak, referenceType, &dpnd)) ) return -1;
	if (!dpnd) return 0;
	return dpnd->trackIDCount;
}


static GF_Err gf_isom_set_ctts_v0(GF_ISOFile *file, GF_TrackBox *trak)
{
	u32 i;
	s32 shift;
	GF_CompositionOffsetBox *ctts;
	GF_CompositionToDecodeBox *cslg;

	ctts = trak->Media->information->sampleTable->CompositionOffset;

	if (!trak->Media->information->sampleTable->CompositionToDecode)
	{
		shift = 0;
		for (i=0; i<ctts->nb_entries; i++) {
			if (-ctts->entries[i].decodingOffset > shift)
				shift = -ctts->entries[i].decodingOffset;
		}
		if (shift > 0)
		{
			for (i=0; i<ctts->nb_entries; i++) {
				ctts->entries[i].decodingOffset += shift;
			}
		}
	}
	else
	{
		cslg = trak->Media->information->sampleTable->CompositionToDecode;
		shift = cslg->compositionToDTSShift;
		for (i=0; i<ctts->nb_entries; i++) {
			ctts->entries[i].decodingOffset += shift;
		}
		gf_isom_box_del_parent(&trak->Media->information->sampleTable->child_boxes, (GF_Box *)cslg);
		trak->Media->information->sampleTable->CompositionToDecode = NULL;
	}
	if (shift>0) {
		//no edits, insert one
		if (! trak->editBox) {
			u64 dur = trak->Media->mediaHeader->duration;
			dur *= file->moov->mvhd->timeScale;
			dur /= trak->Media->mediaHeader->timeScale;
			gf_isom_set_edit(file, gf_list_find(file->moov->trackList, trak)+1, 0, dur, shift, GF_ISOM_EDIT_NORMAL);
		} else {
			//otherwise shift media times in all entries
			for (i=0; i<gf_list_count(trak->editBox->editList->entryList); i++) {
				GF_EdtsEntry *ent = (GF_EdtsEntry*)gf_list_get(trak->editBox->editList->entryList, i);
				//empty edit
				if (ent->mediaTime<0) continue;
				ent->mediaTime += shift;
			}
			SetTrackDuration(trak);
		}
	}
	ctts->version = 0;
	gf_isom_modify_alternate_brand(file, GF_ISOM_BRAND_ISO4, GF_FALSE);
	return GF_OK;
}


GF_Err gf_isom_set_timescale(GF_ISOFile *movie, u32 timeScale)
{
	GF_TrackBox *trak;
	u32 i;
	GF_Err e;
	if (!timeScale) return GF_BAD_PARAM;
	e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE);
	if (e) return e;
	e = gf_isom_insert_moov(movie);
	if (e) return e;

	if (movie->moov->mvhd->timeScale == timeScale) return GF_OK;

	/*rewrite all durations and edit lists*/
	movie->moov->mvhd->duration *= timeScale;
	movie->moov->mvhd->duration /= movie->moov->mvhd->timeScale;
	if (movie->moov->mvex && movie->moov->mvex->mehd) {
		movie->moov->mvex->mehd->fragment_duration *= timeScale;
		movie->moov->mvex->mehd->fragment_duration /= movie->moov->mvhd->timeScale;
	}

	i=0;
	while ((trak = (GF_TrackBox*)gf_list_enum(movie->moov->trackList, &i))) {
		trak->Header->duration *= timeScale;
		trak->Header->duration /= movie->moov->mvhd->timeScale;

		if (trak->editBox && trak->editBox->editList) {
			u32 j, count = gf_list_count(trak->editBox->editList->entryList);
			for (j=0; j<count; j++) {
				GF_EdtsEntry *ent = (GF_EdtsEntry *)gf_list_get(trak->editBox->editList->entryList, j);
				ent->segmentDuration *= timeScale;
				ent->segmentDuration /= movie->moov->mvhd->timeScale;
			}
		}
	}
	if (movie->moov->mvex && movie->moov->mvex->mehd) {
		movie->moov->mvex->mehd->fragment_duration *= timeScale;
		movie->moov->mvex->mehd->fragment_duration /= movie->moov->mvhd->timeScale;
	}
	movie->moov->mvhd->timeScale = timeScale;
	movie->interleavingTime = timeScale;
	return GF_OK;
}


GF_EXPORT
Bool gf_isom_needs_layer_reconstruction(GF_ISOFile *file)
{
	u32 count, i;
	if (!file)
		return GF_FALSE;
	count = gf_isom_get_track_count(file);
	for (i = 0; i < count; i++) {
		if (gf_isom_get_reference_count(file, i+1, GF_ISOM_REF_SCAL) > 0) {
			return GF_TRUE;
		}
		if (gf_isom_get_reference_count(file, i+1, GF_ISOM_REF_SABT) > 0) {
			return GF_TRUE;
		}
		switch (gf_isom_get_media_subtype(file, i+1, 1)) {
		case GF_ISOM_SUBTYPE_LHV1:
		case GF_ISOM_SUBTYPE_LHE1:
		case GF_ISOM_SUBTYPE_HVC2:
		case GF_ISOM_SUBTYPE_HEV2:
			if (gf_isom_get_reference_count(file, i+1, GF_ISOM_REF_BASE) > 0) {
				return GF_TRUE;
			}
		}
	}
	return GF_FALSE;


u32 gf_isom_get_timescale(GF_ISOFile *movie)
{
	if (!movie || !movie->moov || !movie->moov->mvhd) return 0;
	return movie->moov->mvhd->timeScale;
}


GF_Err gf_isom_use_compact_size(GF_ISOFile *movie, u32 trackNumber, Bool CompactionOn)
{
	GF_TrackBox *trak;
	u32 i, size;
	GF_SampleSizeBox *stsz;
	GF_Err e;

	e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE);
	if (e) return e;

	trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak) return GF_BAD_PARAM;

	if (!trak->Media || !trak->Media->information
	        || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->SampleSize)
		return GF_ISOM_INVALID_FILE;

	stsz = trak->Media->information->sampleTable->SampleSize;

	//switch to regular table
	if (!CompactionOn) {
		if (stsz->type == GF_ISOM_BOX_TYPE_STSZ) return GF_OK;
		stsz->type = GF_ISOM_BOX_TYPE_STSZ;
		//invalidate the sampleSize and recompute it
		stsz->sampleSize = 0;
		if (!stsz->sampleCount) return GF_OK;
		//if the table is empty we can only assume the track is empty (no size indication)
		if (!stsz->sizes) return GF_OK;
		size = stsz->sizes[0];
		//check whether the sizes are all the same or not
		for (i=1; i<stsz->sampleCount; i++) {
			if (size != stsz->sizes[i]) {
				size = 0;
				break;
			}
		}
		if (size) {
			gf_free(stsz->sizes);
			stsz->sizes = NULL;
			stsz->sampleSize = size;
		}
		return GF_OK;
	}

	//switch to compact table
	if (stsz->type == GF_ISOM_BOX_TYPE_STZ2) return GF_OK;
	//fill the table. Although it seems weird , this is needed in case of edition
	//after the function is called. NOte however than we force regular table
	//at write time if all samples are of same size
	if (stsz->sampleSize) {
		//this is a weird table indeed ;)
		if (stsz->sizes) gf_free(stsz->sizes);
		stsz->sizes = (u32*) gf_malloc(sizeof(u32)*stsz->sampleCount);
		if (!stsz->sizes) return GF_OUT_OF_MEM;
		memset(stsz->sizes, stsz->sampleSize, sizeof(u32));
	}
	//set the SampleSize to 0 while the file is open
	stsz->sampleSize = 0;
	stsz->type = GF_ISOM_BOX_TYPE_STZ2;
	return GF_OK;
}


Bool gf_isom_has_movie(GF_ISOFile *file)
{
	if (file && file->moov) return GF_TRUE;
	return GF_FALSE;
}


u32 gf_isom_get_track_kind_count(GF_ISOFile *the_file, u32 trackNumber)
{
	GF_UserDataBox *udta;
	GF_UserDataMap *map;
	if (trackNumber) {
		GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber);
		if (!trak) return 0;
		if (!trak->udta) {
			return 0;
		}
		udta = trak->udta;
	} else {
		return 0;
	}
	map = udta_getEntry(udta, GF_ISOM_BOX_TYPE_KIND, NULL);
	if (!map) return 0;

	return gf_list_count(map->boxes);
}


GF_EXPORT
GF_Err gf_isom_get_sample_cenc_info(GF_ISOFile *movie, u32 track, u32 sample_number, Bool *IsEncrypted, u8 *crypt_byte_block, u8 *skip_byte_block, const u8 **key_info, u32 *key_info_size)
{
	GF_TrackBox *trak = gf_isom_get_track_from_file(movie, track);
	GF_SampleEncryptionBox *senc = trak->sample_encryption;

	return gf_isom_get_sample_cenc_info_internal(trak, NULL, senc, sample_number, IsEncrypted, crypt_byte_block, skip_byte_block, key_info, key_info_size);


GF_Err gf_isom_get_trex_template(GF_ISOFile *file, u32 track, u8 **output, u32 *output_size)
{
	GF_TrackBox *trak;
	GF_BitStream *bs;
	u32 i;
	GF_TrackExtendsBox *trex = NULL;
	GF_TrackExtensionPropertiesBox *trexprop = NULL;

	*output = NULL;
	*output_size = 0;
	/*get orig sample desc and clone it*/
	trak = gf_isom_get_track_from_file(file, track);
	if (!trak || !trak->Media) return GF_BAD_PARAM;
	if (!file->moov->mvex) return GF_NOT_FOUND;
	for (i=0; i<gf_list_count(file->moov->mvex->TrackExList); i++) {
		trex = gf_list_get(file->moov->mvex->TrackExList, i);
		if (trex->trackID == trak->Header->trackID) break;
		trex = NULL;
	}
	if (!trex) return GF_NOT_FOUND;

	for (i=0; i<gf_list_count(file->moov->mvex->TrackExPropList); i++) {
		trexprop = gf_list_get(file->moov->mvex->TrackExPropList, i);
		if (trexprop->trackID== trak->Header->trackID) break;
		trexprop = NULL;
	}
	bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
	gf_isom_box_size( (GF_Box *) trex);
	gf_isom_box_write((GF_Box *) trex, bs);

	if (trexprop) {
		gf_isom_box_size( (GF_Box *) trexprop);
		gf_isom_box_write((GF_Box *) trexprop, bs);
	}
	gf_bs_get_content(bs, output, output_size);
	gf_bs_del(bs);

	return GF_OK;

}


GF_Err gf_isom_patch_last_sample_duration(GF_ISOFile *movie, u32 trackNumber, u64 next_dts)
{
	return gf_isom_set_last_sample_duration_internal(movie, trackNumber, next_dts, 0, 2);
}


u64 gf_isom_estimate_size(GF_ISOFile *movie)
{
	GF_Err e;
	GF_Box *a;
	u32 i, count;
	u64 mdat_size;
	if (!movie || !movie->moov) return 0;

	mdat_size = 0;
	count = gf_list_count(movie->moov->trackList);
	for (i=0; i<count; i++) {
		mdat_size += gf_isom_get_media_data_size(movie, i+1);
	}
	if (mdat_size) {
		mdat_size += 8;
		if (mdat_size > 0xFFFFFFFF) mdat_size += 8;
	}

	i=0;
	while ((a = (GF_Box*)gf_list_enum(movie->TopBoxes, &i))) {
		e = gf_isom_box_size(a);
		if (e == GF_OK)
			mdat_size += a->size;
	}
	return mdat_size;
}


GF_Err gf_isom_get_chunks_infos(GF_ISOFile *movie, u32 trackNumber, u32 *dur_min, u32 *dur_avg, u32 *dur_max, u32 *size_min, u32 *size_avg, u32 *size_max)
{
	GF_TrackBox *trak;
	u32 i, k, sample_idx, dmin, dmax, smin, smax, tot_chunks;
	u64 davg, savg;
	GF_SampleToChunkBox *stsc;
	GF_TimeToSampleBox *stts;
	if (!movie || !trackNumber || !movie->moov) return GF_BAD_PARAM;
	trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak) return GF_BAD_PARAM;

	stsc = trak->Media->information->sampleTable->SampleToChunk;
	stts = trak->Media->information->sampleTable->TimeToSample;
	if (!stsc || !stts) return GF_ISOM_INVALID_FILE;

	dmin = smin = (u32) -1;
	dmax = smax = 0;
	davg = savg = 0;
	sample_idx = 1;
	tot_chunks = 0;
	for (i=0; i<stsc->nb_entries; i++) {
		u32 nb_chunk = 0;
		if (stsc->entries[i].samplesPerChunk >  2*trak->Media->information->sampleTable->SampleSize->sampleCount) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] likely broken stco entry (%u samples per chunk but %u samples total)\n", stsc->entries[i].samplesPerChunk, trak->Media->information->sampleTable->SampleSize->sampleCount));
			return GF_ISOM_INVALID_FILE;
		}
		while (1) {
			u32 chunk_dur = 0;
			u32 chunk_size = 0;
			for (k=0; k<stsc->entries[i].samplesPerChunk; k++) {
				u64 dts;
				u32 dur;
				u32 size;
				stbl_GetSampleDTS_and_Duration(stts, k+sample_idx, &dts, &dur);
				chunk_dur += dur;
				stbl_GetSampleSize(trak->Media->information->sampleTable->SampleSize, k+sample_idx, &size);
				chunk_size += size;

			}
			if (dmin>chunk_dur) dmin = chunk_dur;
			if (dmax<chunk_dur) dmax = chunk_dur;
			davg += chunk_dur;
			if (smin>chunk_size) smin = chunk_size;
			if (smax<chunk_size) smax = chunk_size;
			savg += chunk_size;

			tot_chunks ++;
			sample_idx += stsc->entries[i].samplesPerChunk;
			if (i+1==stsc->nb_entries) break;
			nb_chunk ++;
			if (stsc->entries[i].firstChunk + nb_chunk == stsc->entries[i+1].firstChunk) break;
		}
	}
	if (tot_chunks) {
		davg /= tot_chunks;
		savg /= tot_chunks;
	}
	if (dur_min) *dur_min = dmin;
	if (dur_avg) *dur_avg = (u32) davg;
	if (dur_max) *dur_max = dmax;

	if (size_min) *size_min = smin;
	if (size_avg) *size_avg = (u32) savg;
	if (size_max) *size_max = smax;
	return GF_OK;
}


GF_Err gf_isom_remove_edits(GF_ISOFile *movie, u32 trackNumber)
{
	GF_Err e;
	GF_TrackBox *trak;
	trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak) return GF_BAD_PARAM;

	e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE);
	if (e) return e;

	if (!trak->editBox || !trak->editBox->editList) return GF_OK;

	while (gf_list_count(trak->editBox->editList->entryList)) {
		GF_EdtsEntry *ent = (GF_EdtsEntry*)gf_list_get(trak->editBox->editList->entryList, 0);
		gf_free(ent);
		e = gf_list_rem(trak->editBox->editList->entryList, 0);
		if (e) return e;
	}
	//then delete the GF_EditBox...
	gf_isom_box_del_parent(&trak->child_boxes, (GF_Box *)trak->editBox);
	trak->editBox = NULL;
	return SetTrackDuration(trak);
}


Bool gf_isom_has_segment(GF_ISOFile *file, u32 *brand, u32 *version)
{
#ifndef GPAC_DISABLE_ISOM_FRAGMENTS
	u32 i;
	GF_Box *a;
	i = 0;
	while (NULL != (a = (GF_Box*)gf_list_enum(file->TopBoxes, &i))) {
#ifndef GPAC_DISABLE_ISOM_FRAGMENTS
		if (a->type == GF_ISOM_BOX_TYPE_STYP) {
			GF_FileTypeBox *styp = (GF_FileTypeBox *)a;
			*brand = styp->majorBrand;
			*version = styp->minorVersion;
			return GF_TRUE;
		}
#endif
	}
#endif
	return GF_FALSE;
}


GF_Err gf_isom_set_track_enabled(GF_ISOFile *movie, u32 trackNumber, Bool enableTrack)
{
	GF_Err e;
	GF_TrackBox *trak;

	e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE);
	if (e) return e;

	trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak) return GF_BAD_PARAM;

	if (enableTrack) {
		trak->Header->flags |= 1;
	} else {
		trak->Header->flags &= ~1;
	}
	return GF_OK;
}


void gf_media_get_sample_average_infos(GF_ISOFile *file, u32 Track, u32 *avgSize, u32 *MaxSize, u32 *TimeDelta, u32 *maxCTSDelta, u32 *const_duration, u32 *bandwidth)
{
	u32 i, count, ts_diff;
	u64 prevTS, tdelta;
	Double bw;
	GF_ISOSample *samp;

	*avgSize = *MaxSize = 0;
	*TimeDelta = 0;
	*maxCTSDelta = 0;
	bw = 0;
	prevTS = 0;
	tdelta = 0;

	count = gf_isom_get_sample_count(file, Track);
	if (!count) return;
	*const_duration = 0;

	for (i=0; i<count; i++) {
		samp = gf_isom_get_sample_info(file, Track, i+1, NULL, NULL);
		if (!samp) break;

		//get the size
		*avgSize += samp->dataLength;
		if (*MaxSize < samp->dataLength) *MaxSize = samp->dataLength;
		ts_diff = (u32) (samp->DTS+samp->CTS_Offset - prevTS);
		//get the time
		tdelta += ts_diff;

		if (i==1) {
			*const_duration = ts_diff;
		} else if ( (i<count-1) && (*const_duration != ts_diff) ) {
			*const_duration = 0;
		}

		prevTS = samp->DTS+samp->CTS_Offset;
		bw += 8*samp->dataLength;

		//get the CTS delta
		if ((samp->CTS_Offset>=0) && ((u32)samp->CTS_Offset > *maxCTSDelta))
			*maxCTSDelta = samp->CTS_Offset;
		gf_isom_sample_del(&samp);
	}
	if (count>1) *TimeDelta = (u32) (tdelta/ (count-1) );
	else *TimeDelta = (u32) tdelta;
	*avgSize /= count;
	bw *= gf_isom_get_media_timescale(file, Track);
	bw /= (s64) gf_isom_get_media_duration(file, Track);
	bw /= 1000;
	(*bandwidth) = (u32) (bw+0.5);

	//delta is NOT an average, we need to know exactly how many bits are
	//needed to encode CTS-DTS for ANY samples
}


Bool gf_isom_has_padding_bits(GF_ISOFile *the_file, u32 trackNumber)
{
	GF_TrackBox *trak;

	trak = gf_isom_get_track_from_file(the_file, trackNumber);
	if (!trak) return GF_FALSE;

	if (trak->Media->information->sampleTable->PaddingBits) return GF_TRUE;
	return GF_FALSE;
}


static int get_debug_info(struct PE_(r_bin_pe_obj_t)* bin, PE_(image_debug_directory_entry)* dbg_dir_entry, ut8* dbg_data, int dbg_data_len, SDebugInfo* res) {
	#define SIZEOF_FILE_NAME 255
	int i = 0;
	const char* basename;
	if (!dbg_data) {
		return 0;
	}
	switch (dbg_dir_entry->Type) {
	case IMAGE_DEBUG_TYPE_CODEVIEW:
		if (!strncmp ((char*) dbg_data, "RSDS", 4)) {
			SCV_RSDS_HEADER rsds_hdr;
			init_rsdr_hdr (&rsds_hdr);
			if (!get_rsds (dbg_data, dbg_data_len, &rsds_hdr)) {
				bprintf ("Warning: Cannot read PE debug info\n");
				return 0;
			}
			snprintf (res->guidstr, GUIDSTR_LEN,
				"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x%x",
				rsds_hdr.guid.data1,
				rsds_hdr.guid.data2,
				rsds_hdr.guid.data3,
				rsds_hdr.guid.data4[0],
				rsds_hdr.guid.data4[1],
				rsds_hdr.guid.data4[2],
				rsds_hdr.guid.data4[3],
				rsds_hdr.guid.data4[4],
				rsds_hdr.guid.data4[5],
				rsds_hdr.guid.data4[6],
				rsds_hdr.guid.data4[7],
				rsds_hdr.age);
			basename = r_file_basename ((char*) rsds_hdr.file_name);
			strncpy (res->file_name, (const char*)
				basename, sizeof (res->file_name));
			res->file_name[sizeof (res->file_name) - 1] = 0;
			rsds_hdr.free ((struct SCV_RSDS_HEADER*) &rsds_hdr);
		} else if (strncmp ((const char*) dbg_data, "NB10", 4) == 0) {
			if (dbg_data_len < 20) {
				eprintf ("Truncated NB10 entry, not enough data to parse\n");
				return 0;
			}
			SCV_NB10_HEADER nb10_hdr = {{0}};
			init_cv_nb10_header (&nb10_hdr);
			get_nb10 (dbg_data, &nb10_hdr);
			snprintf (res->guidstr, sizeof (res->guidstr),
				"%x%x", nb10_hdr.timestamp, nb10_hdr.age);
			res->file_name[0] = 0;
			if (nb10_hdr.file_name) {
				strncpy (res->file_name, (const char*)
						nb10_hdr.file_name, sizeof (res->file_name) - 1);
			}
			res->file_name[sizeof (res->file_name) - 1] = 0;
			nb10_hdr.free ((struct SCV_NB10_HEADER*) &nb10_hdr);
		} else {
			bprintf ("CodeView section not NB10 or RSDS\n");
			return 0;
		}
		break;
	default:
		//bprintf("get_debug_info(): not supported type\n");
		return 0;
	}

	while (i < 33) {
		res->guidstr[i] = toupper ((int) res->guidstr[i]);
		i++;
	}

	return 1;
}


int PE_(r_bin_pe_is_stripped_local_syms)(struct PE_(r_bin_pe_obj_t)* bin) {
	if (!bin || !bin->nt_headers) {
		return false;
	}
	return HASCHR (PE_IMAGE_FILE_LOCAL_SYMS_STRIPPED);
}


static void bin_pe_store_tls_callbacks(struct PE_(r_bin_pe_obj_t)* bin, PE_DWord callbacks) {
	PE_DWord paddr, haddr;
	int count = 0;
	PE_DWord addressOfTLSCallback = 1;
	char* key;

	while (addressOfTLSCallback != 0) {
		if (r_buf_read_at (bin->b, callbacks, (ut8*) &addressOfTLSCallback, sizeof(addressOfTLSCallback)) != sizeof (addressOfTLSCallback)) {
			bprintf ("Warning: read (tls_callback)\n");
			return;
		}
		if (!addressOfTLSCallback) {
			break;
		}
		if (bin->optional_header->SizeOfImage) {
			int rva_callback = bin_pe_va_to_rva (bin, (PE_DWord) addressOfTLSCallback);
			if (rva_callback > bin->optional_header->SizeOfImage) {
				break;
			}
		}
		key = sdb_fmt ("pe.tls_callback%d_vaddr", count);
		sdb_num_set (bin->kv, key, addressOfTLSCallback, 0);
		key = sdb_fmt ("pe.tls_callback%d_paddr", count);
		paddr = bin_pe_rva_to_paddr (bin, bin_pe_va_to_rva (bin, (PE_DWord) addressOfTLSCallback));
		sdb_num_set (bin->kv, key, paddr,                0);
		key = sdb_fmt ("pe.tls_callback%d_haddr", count);
		haddr = callbacks;
		sdb_num_set (bin->kv, key, haddr,                0);
		count++;
		callbacks += sizeof (addressOfTLSCallback);
	}
}


R_API void PE_(bin_pe_parse_resource)(struct PE_(r_bin_pe_obj_t) *bin) {
	int index = 0;
	ut64 off = 0, rsrc_base = bin->resource_directory_offset;
	Pe_image_resource_directory *rs_directory = bin->resource_directory;
	ut32 curRes = 0;
	int totalRes = 0;
	SdbHash *dirs = sdb_ht_new (); //to avoid infinite loops
	if (!dirs) {
		return;
	}
	if (!rs_directory) {
		sdb_ht_free (dirs);
		return;
	}
	curRes = rs_directory->NumberOfNamedEntries;
	totalRes = curRes + rs_directory->NumberOfIdEntries;
	if (totalRes > R_PE_MAX_RESOURCES) {
		eprintf ("Error parsing resource directory\n");
		sdb_ht_free (dirs);
		return;
	}
	for (index = 0; index < totalRes; index++) {
		Pe_image_resource_directory_entry typeEntry;
		off = rsrc_base + sizeof (*rs_directory) + index * sizeof (typeEntry);
		sdb_ht_insert (dirs, sdb_fmt ("0x%08"PFMT64x, off), "1");
		if (off > bin->size || off + sizeof(typeEntry) > bin->size) {
			break;
		}
		if (r_buf_read_at (bin->b, off, (ut8*)&typeEntry, sizeof(typeEntry)) < 1) {
			eprintf ("Warning: read resource  directory entry\n");
			break;
		}
		if (typeEntry.u2.s.DataIsDirectory) {
			Pe_image_resource_directory identEntry;
			off = rsrc_base + typeEntry.u2.s.OffsetToDirectory;
			int len = r_buf_read_at (bin->b, off, (ut8*)&identEntry, sizeof(identEntry));
			if (len < 1 || len != sizeof (identEntry)) {
				eprintf ("Warning: parsing resource directory\n");
			}
			_parse_resource_directory (bin, &identEntry, typeEntry.u2.s.OffsetToDirectory, typeEntry.u1.Id, 0, dirs);
		}
	}
	sdb_ht_free (dirs);
	_store_resource_sdb (bin);
}


static PE_DWord bin_pe_rva_to_va(RBinPEObj* bin, PE_DWord rva) {
	return PE_(r_bin_pe_get_image_base) (bin) + rva;
}


int PE_(r_bin_pe_get_debug_data)(struct PE_(r_bin_pe_obj_t)* bin, SDebugInfo* res) {
	PE_(image_debug_directory_entry)* img_dbg_dir_entry = NULL;
	PE_(image_data_directory) * dbg_dir;
	PE_DWord dbg_dir_offset;
	ut8* dbg_data = 0;
	int result = 0;
	if (!bin) {
		return 0;
	}
	dbg_dir = &bin->nt_headers->optional_header.DataDirectory[6 /*IMAGE_DIRECTORY_ENTRY_DEBUG*/];
	dbg_dir_offset = bin_pe_rva_to_paddr (bin, dbg_dir->VirtualAddress);
	if ((int) dbg_dir_offset < 0 || dbg_dir_offset >= bin->size) {
		return false;
	}
	if (dbg_dir_offset >= bin->b->length) {
		return false;
	}
	img_dbg_dir_entry = (PE_(image_debug_directory_entry)*)(bin->b->buf + dbg_dir_offset);
	if ((bin->b->length - dbg_dir_offset) < sizeof (PE_(image_debug_directory_entry))) {
		return false;
	}
	if (img_dbg_dir_entry) {
		ut32 dbg_data_poff = R_MIN (img_dbg_dir_entry->PointerToRawData, bin->b->length);
		int dbg_data_len = R_MIN (img_dbg_dir_entry->SizeOfData, bin->b->length - dbg_data_poff);
		if (dbg_data_len < 1) {
			return false;
		}
		dbg_data = (ut8*) calloc (1, dbg_data_len + 1);
		if (dbg_data) {
			r_buf_read_at (bin->b, dbg_data_poff, dbg_data, dbg_data_len);
			result = get_debug_info (bin, img_dbg_dir_entry, dbg_data, dbg_data_len, res);
			R_FREE (dbg_data);
		}
	}
	return result;
}


static gboolean prpl_xfer_write(struct file_transfer *ft, char *buffer, unsigned int len)
{
	struct prpl_xfer_data *px = ft->data;

	if (write(px->fd, buffer, len) != len) {
		imcb_file_canceled(px->ic, ft, "Error while writing temporary file");
		return FALSE;
	}

	if (lseek(px->fd, 0, SEEK_CUR) >= ft->file_size) {
		close(px->fd);
		px->fd = -1;

		purple_transfer_forward(ft);
		imcb_file_finished(px->ic, ft);
		px->ft = NULL;
	} else {
		px->timeout = b_timeout_add(0, purple_transfer_request_cb, ft);
	}

	return TRUE;
}


static void prpl_xfer_free(struct file_transfer *ft)
{
	struct prpl_xfer_data *px = ft->data;
	struct purple_data *pd = px->ic->proto_data;

	pd->filetransfers = g_slist_remove(pd->filetransfers, px);

	if (px->xfer) {
		px->xfer->ui_data = NULL;
		purple_xfer_unref(px->xfer);
	}

	if (px->timeout) {
		b_event_remove(px->timeout);
	}

	g_free(px->fn);
	g_free(px->handle);
	if (px->fd >= 0) {
		close(px->fd);
	}
	g_free(px);
}


static void ghash_flush(struct ghash_ctx *ctx, struct ghash_desc_ctx *dctx)
{
	u8 *dst = dctx->buffer;

	if (dctx->bytes) {
		u8 *tmp = dst + (GHASH_BLOCK_SIZE - dctx->bytes);

		while (dctx->bytes--)
			*tmp++ ^= 0;

		gf128mul_4k_lle((be128 *)dst, ctx->gf128);
	}

	dctx->bytes = 0;
}


static int ghash_init(struct shash_desc *desc)
{
	struct ghash_desc_ctx *dctx = shash_desc_ctx(desc);

	memset(dctx, 0, sizeof(*dctx));

	return 0;
}


  void Compute(OpKernelContext* context) override {
    const Tensor& tensor_in = context->input(0);
    const Tensor& tensor_out = context->input(1);
    const Tensor& out_grad_backprop = context->input(2);

    // For maxpooling, tensor_in should have 4 dimensions.
    OP_REQUIRES(context, tensor_in.dims() == 4,
                errors::InvalidArgument("tensor_in must be 4-dimensional 4"));
    OP_REQUIRES(context, tensor_out.dims() == 4,
                errors::InvalidArgument("tensor_out must be 4-dimensional"));
    // For maxpooling, out_grad_backprop should have 4 dimensions.
    OP_REQUIRES(
        context, out_grad_backprop.dims() == 4,
        errors::InvalidArgument("out_grad_backprop must be 4-dimensional"));

    Tensor* output = nullptr;
    OP_REQUIRES_OK(context,
                   context->allocate_output(0, tensor_out.shape(), &output));

    std::vector<int32> ksize = ksize_;
    std::vector<int32> stride = stride_;
    if (context->num_inputs() == 5) {
      const Tensor& tensor_ksize = context->input(3);
      auto value_ksize = tensor_ksize.flat<int32>();
      ksize.resize(tensor_ksize.shape().num_elements());
      std::copy_n(&value_ksize(0), ksize.size(), ksize.begin());

      const Tensor& tensor_stride = context->input(4);
      auto value_stride = tensor_stride.flat<int32>();
      stride.resize(tensor_stride.shape().num_elements());
      std::copy_n(&value_stride(0), stride.size(), stride.begin());
    }

    OP_REQUIRES(context, ksize.size() == 4,
                errors::InvalidArgument("Sliding window ksize field must "
                                        "specify 4 dimensions"));
    OP_REQUIRES(context, stride.size() == 4,
                errors::InvalidArgument("Sliding window strides field must "
                                        "specify 4 dimensions"));
    const int32 ksize_n = GetTensorDim(ksize, data_format_, 'N');
    const int32 stride_n = GetTensorDim(stride, data_format_, 'N');
    OP_REQUIRES(context, ksize_n == 1 && stride_n == 1,
                errors::Unimplemented(
                    "Pooling is not yet supported on the batch dimension."));

    PoolParameters params{context,
                          ksize,
                          stride,
                          padding_,
                          /*explicit_paddings=*/{},
                          data_format_,
                          tensor_in.shape()};

    functor::MaxPoolGradBackwardNoMask<T>()(
        data_format_, tensor_in.flat<T>().data(), tensor_out.flat<T>().data(),
        params.tensor_in_batch, params.out_height, params.out_width,
        params.depth, params.tensor_in_rows, params.tensor_in_cols,
        params.window_rows, params.window_cols, params.row_stride,
        params.col_stride, params.pad_top, params.pad_left,
        out_grad_backprop.flat<T>().data(), output->flat<T>().data(),
        context->eigen_device<Eigen::GpuDevice>());
  }


  void Compute(OpKernelContext* context) override {
    const Tensor& tensor_in = context->input(0);

    std::vector<int32> ksize = ksize_;
    std::vector<int32> stride = stride_;

    if (context->num_inputs() != 1) {
      const Tensor& tensor_ksize = context->input(1);
      auto value_ksize = tensor_ksize.flat<int32>();
      ksize.resize(tensor_ksize.shape().num_elements());
      std::copy_n(&value_ksize(0), ksize.size(), ksize.begin());

      const Tensor& tensor_stride = context->input(2);
      auto value_stride = tensor_stride.flat<int32>();
      stride.resize(tensor_stride.shape().num_elements());
      std::copy_n(&value_stride(0), stride.size(), stride.begin());
    }
    OP_REQUIRES(context, ksize.size() == 4,
                errors::InvalidArgument("Sliding window ksize field must "
                                        "specify 4 dimensions"));
    OP_REQUIRES(context, stride.size() == 4,
                errors::InvalidArgument("Sliding window stride field must "
                                        "specify 4 dimensions"));
    const int32 ksize_n = GetTensorDim(ksize, data_format_, 'N');
    const int32 stride_n = GetTensorDim(stride, data_format_, 'N');
    OP_REQUIRES(context, ksize_n == 1 && stride_n == 1,
                errors::Unimplemented(
                    "Pooling is not yet supported on the batch dimension."));

    PoolParameters params{context,
                          ksize,
                          stride,
                          padding_,
                          /*explicit_paddings=*/{},
                          data_format_,
                          tensor_in.shape()};
    if (!context->status().ok()) {
      return;
    }

    TensorShape out_shape =
        ShapeFromFormat(data_format_, params.tensor_in_batch, params.out_height,
                        params.out_width, params.depth);
    if (data_format_ == FORMAT_NCHW) {
      DnnPoolingOp<T>::Compute(context, se::dnn::PoolingMode::kMaximum, ksize,
                               stride, padding_, explicit_paddings_,
                               data_format_, tensor_in, out_shape,
                               propagate_nans_);
    } else {
      CHECK(data_format_ == FORMAT_NHWC)
          << "MaxPool only supports NCHW or NHWC format";
      Tensor* output = nullptr;
      OP_REQUIRES_OK(context, context->allocate_output(0, out_shape, &output));
      LaunchMaxPoolingNoMask<Device, T>::launch(context, params, tensor_in,
                                                output, propagate_nans_);
    }
  }


long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
		   unsigned int flags)
{
	struct fuse_file *ff = file->private_data;
	struct fuse_conn *fc = ff->fc;
	struct fuse_ioctl_in inarg = {
		.fh = ff->fh,
		.cmd = cmd,
		.arg = arg,
		.flags = flags
	};
	struct fuse_ioctl_out outarg;
	struct fuse_req *req = NULL;
	struct page **pages = NULL;
	struct page *iov_page = NULL;
	struct iovec *in_iov = NULL, *out_iov = NULL;
	unsigned int in_iovs = 0, out_iovs = 0, num_pages = 0, max_pages;
	size_t in_size, out_size, transferred;
	int err;

	/* assume all the iovs returned by client always fits in a page */
	BUILD_BUG_ON(sizeof(struct iovec) * FUSE_IOCTL_MAX_IOV > PAGE_SIZE);

	err = -ENOMEM;
	pages = kzalloc(sizeof(pages[0]) * FUSE_MAX_PAGES_PER_REQ, GFP_KERNEL);
	iov_page = alloc_page(GFP_KERNEL);
	if (!pages || !iov_page)
		goto out;

	/*
	 * If restricted, initialize IO parameters as encoded in @cmd.
	 * RETRY from server is not allowed.
	 */
	if (!(flags & FUSE_IOCTL_UNRESTRICTED)) {
		struct iovec *iov = page_address(iov_page);

		iov->iov_base = (void __user *)arg;
		iov->iov_len = _IOC_SIZE(cmd);

		if (_IOC_DIR(cmd) & _IOC_WRITE) {
			in_iov = iov;
			in_iovs = 1;
		}

		if (_IOC_DIR(cmd) & _IOC_READ) {
			out_iov = iov;
			out_iovs = 1;
		}
	}

 retry:
	inarg.in_size = in_size = iov_length(in_iov, in_iovs);
	inarg.out_size = out_size = iov_length(out_iov, out_iovs);

	/*
	 * Out data can be used either for actual out data or iovs,
	 * make sure there always is at least one page.
	 */
	out_size = max_t(size_t, out_size, PAGE_SIZE);
	max_pages = DIV_ROUND_UP(max(in_size, out_size), PAGE_SIZE);

	/* make sure there are enough buffer pages and init request with them */
	err = -ENOMEM;
	if (max_pages > FUSE_MAX_PAGES_PER_REQ)
		goto out;
	while (num_pages < max_pages) {
		pages[num_pages] = alloc_page(GFP_KERNEL | __GFP_HIGHMEM);
		if (!pages[num_pages])
			goto out;
		num_pages++;
	}

	req = fuse_get_req(fc);
	if (IS_ERR(req)) {
		err = PTR_ERR(req);
		req = NULL;
		goto out;
	}
	memcpy(req->pages, pages, sizeof(req->pages[0]) * num_pages);
	req->num_pages = num_pages;

	/* okay, let's send it to the client */
	req->in.h.opcode = FUSE_IOCTL;
	req->in.h.nodeid = ff->nodeid;
	req->in.numargs = 1;
	req->in.args[0].size = sizeof(inarg);
	req->in.args[0].value = &inarg;
	if (in_size) {
		req->in.numargs++;
		req->in.args[1].size = in_size;
		req->in.argpages = 1;

		err = fuse_ioctl_copy_user(pages, in_iov, in_iovs, in_size,
					   false);
		if (err)
			goto out;
	}

	req->out.numargs = 2;
	req->out.args[0].size = sizeof(outarg);
	req->out.args[0].value = &outarg;
	req->out.args[1].size = out_size;
	req->out.argpages = 1;
	req->out.argvar = 1;

	fuse_request_send(fc, req);
	err = req->out.h.error;
	transferred = req->out.args[1].size;
	fuse_put_request(fc, req);
	req = NULL;
	if (err)
		goto out;

	/* did it ask for retry? */
	if (outarg.flags & FUSE_IOCTL_RETRY) {
		char *vaddr;

		/* no retry if in restricted mode */
		err = -EIO;
		if (!(flags & FUSE_IOCTL_UNRESTRICTED))
			goto out;

		in_iovs = outarg.in_iovs;
		out_iovs = outarg.out_iovs;

		/*
		 * Make sure things are in boundary, separate checks
		 * are to protect against overflow.
		 */
		err = -ENOMEM;
		if (in_iovs > FUSE_IOCTL_MAX_IOV ||
		    out_iovs > FUSE_IOCTL_MAX_IOV ||
		    in_iovs + out_iovs > FUSE_IOCTL_MAX_IOV)
			goto out;

		vaddr = kmap_atomic(pages[0], KM_USER0);
		err = fuse_copy_ioctl_iovec(page_address(iov_page), vaddr,
					    transferred, in_iovs + out_iovs,
					    (flags & FUSE_IOCTL_COMPAT) != 0);
		kunmap_atomic(vaddr, KM_USER0);
		if (err)
			goto out;

		in_iov = page_address(iov_page);
		out_iov = in_iov + in_iovs;

		err = fuse_verify_ioctl_iov(in_iov, in_iovs);
		if (err)
			goto out;

		err = fuse_verify_ioctl_iov(out_iov, out_iovs);
		if (err)
			goto out;

		goto retry;
	}

	err = -EIO;
	if (transferred > inarg.out_size)
		goto out;

	err = fuse_ioctl_copy_user(pages, out_iov, out_iovs, transferred, true);
 out:
	if (req)
		fuse_put_request(fc, req);
	if (iov_page)
		__free_page(iov_page);
	while (num_pages)
		__free_page(pages[--num_pages]);
	kfree(pages);

	return err ? err : outarg.result;
}


TRIGGER_FUNC(mod_wstunnel_handle_trigger) {
    const plugin_data * const p = p_d;
    const unix_time64_t cur_ts = log_monotonic_secs + 1;

    gw_handle_trigger(srv, p_d);

    for (connection *con = srv->conns; con; con = con->next) {
        request_st * const r = &con->request;
        handler_ctx *hctx = r->plugin_ctx[p->id];
        if (NULL == hctx || r->handler_module != p->self)
            continue;

        if (hctx->gw.state != GW_STATE_WRITE && hctx->gw.state != GW_STATE_READ)
            continue;

        if (cur_ts - con->read_idle_ts > r->conf.max_read_idle) {
            DEBUG_LOG_INFO("timeout client (fd=%d)", con->fd);
            mod_wstunnel_frame_send(hctx,MOD_WEBSOCKET_FRAME_TYPE_CLOSE,NULL,0);
            gw_handle_request_reset(r, p_d);
            joblist_append(con);
            /* avoid server.c closing connection with error due to max_read_idle
             * (might instead run joblist after plugins_call_handle_trigger())*/
            con->read_idle_ts = cur_ts;
            continue;
        }

        if (0 != hctx->hybivers
            && hctx->conf.ping_interval > 0
            && (int32_t)hctx->conf.ping_interval + hctx->ping_ts < cur_ts) {
            hctx->ping_ts = cur_ts;
            mod_wstunnel_frame_send(hctx, MOD_WEBSOCKET_FRAME_TYPE_PING, CONST_STR_LEN("ping"));
            joblist_append(con);
            continue;
        }
    }

    return HANDLER_GO_ON;
}


static void mod_wstunnel_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
    switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
      case 0: /* wstunnel.server */
        if (cpv->vtype == T_CONFIG_LOCAL) {
            gw_plugin_config * const gw = cpv->v.v;
            pconf->gw.exts      = gw->exts;
            pconf->gw.exts_auth = gw->exts_auth;
            pconf->gw.exts_resp = gw->exts_resp;
        }
        break;
      case 1: /* wstunnel.balance */
        /*if (cpv->vtype == T_CONFIG_LOCAL)*//*always true here for this param*/
            pconf->gw.balance = (int)cpv->v.u;
        break;
      case 2: /* wstunnel.debug */
        pconf->gw.debug = (int)cpv->v.u;
        break;
      case 3: /* wstunnel.map-extensions */
        pconf->gw.ext_mapping = cpv->v.a;
        break;
      case 4: /* wstunnel.frame-type */
        pconf->frame_type = cpv->v.u;
        break;
      case 5: /* wstunnel.origins */
        pconf->origins = cpv->v.a;
        break;
      case 6: /* wstunnel.ping-interval */
        pconf->ping_interval = cpv->v.shrt;
        break;
      default:/* should not happen */
        return;
    }
}


static int create_response_rfc_6455(handler_ctx *hctx) {
    request_st * const r = hctx->gw.r;
  if (r->http_version == HTTP_VERSION_1_1) {
    SHA_CTX sha;
    unsigned char sha_digest[SHA_DIGEST_LENGTH];

    const buffer *value_wskey =
      http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Key"));
    if (NULL == value_wskey) {
        DEBUG_LOG_ERR("%s", "Sec-WebSocket-Key is invalid");
        return -1;
    }

    /* get SHA1 hash of key */
    /* refer: RFC-6455 Sec.1.3 Opening Handshake */
    SHA1_Init(&sha);
    SHA1_Update(&sha, (const unsigned char *)BUF_PTR_LEN(value_wskey));
    SHA1_Update(&sha, (const unsigned char *)CONST_STR_LEN("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
    SHA1_Final(sha_digest, &sha);

    http_header_response_set(r, HTTP_HEADER_UPGRADE,
                             CONST_STR_LEN("Upgrade"),
                             CONST_STR_LEN("websocket"));
  #if 0 /*(added later in http_response_write_header())*/
    http_header_response_append(r, HTTP_HEADER_CONNECTION,
                                CONST_STR_LEN("Connection"),
                                CONST_STR_LEN("upgrade"));
  #endif

    buffer * const value =
      http_header_response_set_ptr(r, HTTP_HEADER_OTHER,
                                   CONST_STR_LEN("Sec-WebSocket-Accept"));
    buffer_append_base64_encode(value, sha_digest, SHA_DIGEST_LENGTH, BASE64_STANDARD);
  }

    if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_BIN)
        http_header_response_set(r, HTTP_HEADER_OTHER,
                                 CONST_STR_LEN("Sec-WebSocket-Protocol"),
                                 CONST_STR_LEN("binary"));
    else if (-1 == hctx->subproto)
        http_header_response_set(r, HTTP_HEADER_OTHER,
                                 CONST_STR_LEN("Sec-WebSocket-Protocol"),
                                 CONST_STR_LEN("base64"));

    return 0;
}


static void wstunnel_backend_error(gw_handler_ctx *gwhctx) {
    handler_ctx *hctx = (handler_ctx *)gwhctx;
    if (hctx->gw.state == GW_STATE_WRITE || hctx->gw.state == GW_STATE_READ) {
        mod_wstunnel_frame_send(hctx, MOD_WEBSOCKET_FRAME_TYPE_CLOSE, CONST_STR_LEN("1001")); /* 1001 Going Away */
    }
}