<commit_before>import { Filemanager } from './filemanager.js';
import { UI } from './ui.js';
import { Loader } from './loader.js';
import { Plugin } from './plugin.js';
import { Greenworks } from './greenworks.js';
import { Package } from './package.js';

const CCLOADER_VERSION = '2.19.0';

export class ModLoader {
	constructor() {
		this.filemanager = new Filemanager(this);
		this.ui = new UI(this);
		this.loader = new Loader(this.filemanager);

		this.overlay = document.getElementById('overlay');
		this.status = document.getElementById('status');

		/** @type {Mod[]} */
		this.mods = [];
		/** @type {{[name: string]: string}} */
		this.versions = {};

	}

	/**
	 * Loads and starts the game. It then loads the definitions and mods
	 */
	async startGame() {
		await this._buildCrosscodeVersion();

		await this.loader.initialize();

		await this._loadModPackages();
		this._orderCheckMods();
		this._registerMods();

		this._setupGamewindow();

		await this._loadPlugins();
		await this._executePreload();

		this.loader.setStatus('Loading Game');
		await this.loader.startGame(); //Executes until postload

		await this._executePostload();
		this.loader.continue();
		await this._waitForGame();

		// At this point the game UI has become interactive, though the legacy
		// "main" entrypoint is loaded a couple hundred milliseconds later, so
		// CCLoader status overlay is still visible. I think it will result in
		// better UX if we give the user an "illusion" of interactivity before
		// "main" is removed for good.
		window.focus();

		await this._executeMain();


		this._fireLoadEvent();
		this.loader.removeOverlay();
		// Re-focus the game iframe a second time because at this point CCLoader has
		// **really** finished loading the game along with the active mods.
		window.focus();
	}

	async _initializeServiceWorker() {
		this._serviceWorker = await this.filemanager.loadServiceWorker('serviceworker.js', window);
	}

	/**
	 * Notifies filemanager and the serviceworker about existing packed mods.
	 * @param {string[]} packedMods
	 */
	async _loadPackedMods(packedMods) {
		const names = packedMods.map((m) => m.substring(12, m.length));
		await this._sendPackedModNames(packedMods);
		this.filemanager.setPackedMods(names);
	}

	/**
	 *
	 * Notifies the serviceworker about existing packed mods.
	 * @param {string[]} packedMods
	 */
	async _sendPackedModNames(packedMods) {
		const caches = window.caches;
		const keys = await caches.keys();
		await Promise.all(keys.map(name => caches.delete(name)));

		const packedCache = await caches.open('packedMods');
		const dummyResponse = () => new Response('', { status: 200 });
		// CacheStorage does not like the chrome-extension:// schema
		const dummyPrefix = 'http://localhost/';
		await Promise.all(packedMods.map(path => packedCache.put(dummyPrefix + path, dummyResponse())));
	}

	async _buildCrosscodeVersion() {
		try {
			const { changelog } = JSON.parse(await this.filemanager.getResourceAsync('assets/data/changelog.json'));
			this.ccVersion = changelog[0].version;
		} catch (e) {
			let ccVersion = localStorage.getItem('cc.version');
			if (ccVersion) {
				const json = JSON.parse(ccVersion);
				this.ccVersion = json.major + '.' + json.minor + '.' + json.patch;
			} else {
				console.error('Could not find crosscode version. Assuming "0.0.0".', e);
				this.ccVersion = '0.0.0';
			}
		}
	}

	/**
	 * Loads the package.json of the mods. This makes sure all necessary data needed for loading the mod is available.
	 * @returns {Promise<void>}
	 */
	async _loadModPackages() {
		const modFiles = this.filemanager.getAllModsFiles();
		const ccmodFiles = this.filemanager.getAllCCModFiles();
		const packedMods = this.filemanager.getAllModPackages();

		if (packedMods.length > 0) {
			await this._initializeServiceWorker();
			await this._loadPackedMods(packedMods);
			for (const packed of packedMods) {
				modFiles.push(packed.substring(0, packed.length) + '/package.json');
			}
		}

		/** @type {Package[]} */
		const packages = [];
		for (const modFile of modFiles) {
			packages.push(new Package(this, modFile));
		}
		for (const ccmodFile of ccmodFiles) {
			const pkg = new Package(this, ccmodFile, true);

			const existing = packages.findIndex(p => p.baseDirectory === pkg.baseDirectory);
			if (existing) {
				packages.splice(existing, 1);
			}

			packages.push(pkg);
		}

		this.mods = await Promise.all(packages.map(pkg => pkg.load()));
	}

	/**
	 * Orders mods and checks their dependencies. Simplify is always loaded first.
	 */
	_orderCheckMods() {
		const mods = [];

		let lastCount = 0;
		while (this.mods.length != lastCount) {
			lastCount = this.mods.length;
			for (let i = this.mods.length - 1; i >= 0; i--) {<commit_msg>BUGFIX ccmod.json and package.json duplicate check It no longer deletes the last mod package if ccmod.json is unique<commit_after>import { Filemanager } from './filemanager.js';
import { UI } from './ui.js';
import { Loader } from './loader.js';
import { Plugin } from './plugin.js';
import { Greenworks } from './greenworks.js';
import { Package } from './package.js';

const CCLOADER_VERSION = '2.19.0';

export class ModLoader {
	constructor() {
		this.filemanager = new Filemanager(this);
		this.ui = new UI(this);
		this.loader = new Loader(this.filemanager);

		this.overlay = document.getElementById('overlay');
		this.status = document.getElementById('status');

		/** @type {Mod[]} */
		this.mods = [];
		/** @type {{[name: string]: string}} */
		this.versions = {};

	}

	/**
	 * Loads and starts the game. It then loads the definitions and mods
	 */
	async startGame() {
		await this._buildCrosscodeVersion();

		await this.loader.initialize();

		await this._loadModPackages();
		this._orderCheckMods();
		this._registerMods();

		this._setupGamewindow();

		await this._loadPlugins();
		await this._executePreload();

		this.loader.setStatus('Loading Game');
		await this.loader.startGame(); //Executes until postload

		await this._executePostload();
		this.loader.continue();
		await this._waitForGame();

		// At this point the game UI has become interactive, though the legacy
		// "main" entrypoint is loaded a couple hundred milliseconds later, so
		// CCLoader status overlay is still visible. I think it will result in
		// better UX if we give the user an "illusion" of interactivity before
		// "main" is removed for good.
		window.focus();

		await this._executeMain();


		this._fireLoadEvent();
		this.loader.removeOverlay();
		// Re-focus the game iframe a second time because at this point CCLoader has
		// **really** finished loading the game along with the active mods.
		window.focus();
	}

	async _initializeServiceWorker() {
		this._serviceWorker = await this.filemanager.loadServiceWorker('serviceworker.js', window);
	}

	/**
	 * Notifies filemanager and the serviceworker about existing packed mods.
	 * @param {string[]} packedMods
	 */
	async _loadPackedMods(packedMods) {
		const names = packedMods.map((m) => m.substring(12, m.length));
		await this._sendPackedModNames(packedMods);
		this.filemanager.setPackedMods(names);
	}

	/**
	 *
	 * Notifies the serviceworker about existing packed mods.
	 * @param {string[]} packedMods
	 */
	async _sendPackedModNames(packedMods) {
		const caches = window.caches;
		const keys = await caches.keys();
		await Promise.all(keys.map(name => caches.delete(name)));

		const packedCache = await caches.open('packedMods');
		const dummyResponse = () => new Response('', { status: 200 });
		// CacheStorage does not like the chrome-extension:// schema
		const dummyPrefix = 'http://localhost/';
		await Promise.all(packedMods.map(path => packedCache.put(dummyPrefix + path, dummyResponse())));
	}

	async _buildCrosscodeVersion() {
		try {
			const { changelog } = JSON.parse(await this.filemanager.getResourceAsync('assets/data/changelog.json'));
			this.ccVersion = changelog[0].version;
		} catch (e) {
			let ccVersion = localStorage.getItem('cc.version');
			if (ccVersion) {
				const json = JSON.parse(ccVersion);
				this.ccVersion = json.major + '.' + json.minor + '.' + json.patch;
			} else {
				console.error('Could not find crosscode version. Assuming "0.0.0".', e);
				this.ccVersion = '0.0.0';
			}
		}
	}

	/**
	 * Loads the package.json of the mods. This makes sure all necessary data needed for loading the mod is available.
	 * @returns {Promise<void>}
	 */
	async _loadModPackages() {
		const modFiles = this.filemanager.getAllModsFiles();
		const ccmodFiles = this.filemanager.getAllCCModFiles();
		const packedMods = this.filemanager.getAllModPackages();

		if (packedMods.length > 0) {
			await this._initializeServiceWorker();
			await this._loadPackedMods(packedMods);
			for (const packed of packedMods) {
				modFiles.push(packed.substring(0, packed.length) + '/package.json');
			}
		}

		/** @type {Package[]} */
		const packages = [];
		for (const modFile of modFiles) {
			packages.push(new Package(this, modFile));
		}
		for (const ccmodFile of ccmodFiles) {
			const pkg = new Package(this, ccmodFile, true);

			const existing = packages.findIndex(p => p.baseDirectory === pkg.baseDirectory);
			if (existing > -1) {
				packages.splice(existing, 1);
			}

			packages.push(pkg);
		}

		this.mods = await Promise.all(packages.map(pkg => pkg.load()));
	}

	/**
	 * Orders mods and checks their dependencies. Simplify is always loaded first.
	 */
	_orderCheckMods() {
		const mods = [];

		let lastCount = 0;
		while (this.mods.length != lastCount) {
			lastCount = this.mods.length;
			for (let i = this.mods.length - 1; i >= 0; i--) {
{'fraction_non_alphanumeric': 0.13929465776293823, 'fraction_numerical': 0.004695325542570952, 'mean_word_length': 3.7403560830860534, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 22, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '201717', 'n_tokens_mistral': 3319, 'n_tokens_neox': 2989, 'n_words': 806}