<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Archivos de Programación - Detrás del último no va nadie</title>
	<atom:link href="https://blog.krusher.net/category/informatica/programacion/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.krusher.net/category/informatica/programacion/</link>
	<description>Porque alguien tenía que pensar en los peces</description>
	<lastBuildDate>Tue, 06 May 2025 14:50:27 +0000</lastBuildDate>
	<language>es</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://blog.krusher.net/wp-content/uploads/2016/02/cropped-detras-del-ultimo-no-va-nadie-icon-32x32.jpg</url>
	<title>Archivos de Programación - Detrás del último no va nadie</title>
	<link>https://blog.krusher.net/category/informatica/programacion/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>De cómo clonar una instancia de Prestashop</title>
		<link>https://blog.krusher.net/2025/05/de-como-clonar-una-instancia-de-prestashop/</link>
					<comments>https://blog.krusher.net/2025/05/de-como-clonar-una-instancia-de-prestashop/#respond</comments>
		
		<dc:creator><![CDATA[Krusher]]></dc:creator>
		<pubDate>Tue, 06 May 2025 14:49:41 +0000</pubDate>
				<category><![CDATA[Informática]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[Sysadmin]]></category>
		<category><![CDATA[clonar]]></category>
		<category><![CDATA[prestashop]]></category>
		<category><![CDATA[sh]]></category>
		<guid isPermaLink="false">https://blog.krusher.net/?p=2811</guid>

					<description><![CDATA[<p>Últimamente he estado ayudando a mi vecino con su web de Prestashop. He aprendido a hacer algunos truquillos, como editar el aspecto general de la web, hacer y deshacer backups con un entorno limitado pero mi mejor invento ha sido el de clonar la instancia de Prestashop para tener una de test donde probar sin &#8230; <a href="https://blog.krusher.net/2025/05/de-como-clonar-una-instancia-de-prestashop/" class="more-link">Continuar leyendo<span class="screen-reader-text"> "De cómo clonar una instancia de Prestashop"</span></a></p>
<p>La entrada <a href="https://blog.krusher.net/2025/05/de-como-clonar-una-instancia-de-prestashop/">De cómo clonar una instancia de Prestashop</a> se publicó primero en <a href="https://blog.krusher.net">Detrás del último no va nadie</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Últimamente he estado ayudando a mi vecino con su web de Prestashop. He aprendido a hacer algunos truquillos, como editar el aspecto general de la web, hacer y deshacer backups con un entorno limitado pero mi mejor invento ha sido el de clonar la instancia de Prestashop para tener una de test donde probar sin miedo a romper cosas. Aquí el código para quien le pueda ser de utilidad, <a href="https://www.gnu.org/licenses/gpl-3.0.html#license-text" target="_blank" rel="noopener">licenciado bajo GPL 3</a>.</p>
<p>El código puede verse en: <a href="https://gist.github.com/axelei/dafd305213ed1b2655545f06edfbc905" target="_blank" rel="noopener">https://gist.github.com/axelei/dafd305213ed1b2655545f06edfbc905</a></p>
<pre class="brush: bash; title: ; notranslate">
# Programa para clonar prestashop
# Copyright Krusher 2015 - Licenciado bajo GPL3

paramfile=&quot;httpdocs/app/config/parameters.php&quot;
extractDatabaseName=&quot;s/.*&#039;database_name&#039; =&gt; &#039;\(.*\)&#039;.*/\1/p&quot;
extractDatabaseUser=&quot;s/.*&#039;database_user&#039; =&gt; &#039;\(.*\)&#039;.*/\1/p&quot;
extractDatabasePassword=&quot;s/.*&#039;database_password&#039; =&gt; &#039;\(.*\)&#039;.*/\1/p&quot;

if &#x5B; &quot;$#&quot; -lt 4 ]; then
  echo &quot;Error: Se requieren 3 argumentos.&quot;
  echo &quot;Uso: $0 dir_web_original dir_web_destino url_original url_destino&quot;
  exit 1
fi

echo Traspasando de $1 a $2 \(urls: $3 -- $4 \)

databaseName=$(sed -n &quot;$extractDatabaseName&quot; $1/app/config/parameters.php)
databaseUser=$(sed -n &quot;$extractDatabaseUser&quot; $1/app/config/parameters.php)
databasePassword=$(sed -n &quot;$extractDatabasePassword&quot; $1/app/config/parameters.php)

echo Datos de la base de datos de origen: $databaseName / $databaseUser / $databasePassword

targetName=$(sed -n &quot;$extractDatabaseName&quot; $2/app/config/parameters.php)
targetUser=$(sed -n &quot;$extractDatabaseUser&quot; $2/app/config/parameters.php)
targetPassword=$(sed -n &quot;$extractDatabasePassword&quot; $2/app/config/parameters.php)

echo Datos de la base de datos de destino: $targetName / $targetUser / $targetPassword

if &#x5B;&#x5B; -z &quot;$databaseName&quot; ]] || &#x5B;&#x5B; -z &quot;$databaseUser&quot; ]] || &#x5B;&#x5B; -z &quot;$databasePassword&quot; ]] || &#x5B;&#x5B; -z &quot;$targetName&quot; ]] || &#x5B;&#x5B; -z &quot;$targetUser&quot; ]] || &#x5B;&#x5B; -z &quot;$targetPassword&quot; ]]; then
  echo &quot;Error: Algunas de las variables no han sido extraídas, comprueba los errores y los directorios.&quot;
  exit 1
fi

echo URL de la instancia origen: $3
echo URL de la instancia destino: $4

read -p &quot;Pulsa enter para continuar o CTRL+C para cancelar&quot;
echo  &quot;Procediendo con el traspaso.&quot;

echo Extrayendo base de datos
rm -f temp.sql
mysqldump -u $databaseUser $databaseName -p$databasePassword &gt; temp.sql
echo Borrando base de datos objetivo
mysql -u $targetUser $targetName -p$targetPassword -e &quot;drop database $targetName; create database $targetName&quot;
echo Insertando la base de datos origen en la objetivo
mysql -u $targetUser -D $targetName -p$targetPassword &lt; temp.sql
rm -f temp.sql

echo borra ficheros contenido antiguo
rm -Rf $2/*
echo copia ficheros
cp -r $1/* $2/

echo Configurando base de datos objetivo con los datos originales

sed -i &quot;s/&#039;database_name&#039; =&gt; &#039;\(.*\)&#039;/&#039;database_name&#039; =&gt; &#039;$targetName&#039;/g&quot; $2/app/config/parameters.php
sed -i &quot;s/&#039;database_user&#039; =&gt; &#039;\(.*\)&#039;/&#039;database_user&#039; =&gt; &#039;$targetUser&#039;/g&quot; $2/app/config/parameters.php
sed -i &quot;s/&#039;database_password&#039; =&gt; &#039;\(.*\)&#039;/&#039;database_password&#039; =&gt; &#039;$targetPassword&#039;/g&quot; $2/app/config/parameters.php

echo Configurando la nueva URL en la instancia destino

mysql -u $targetUser $targetName -p$targetPassword -e &quot;update prstshp_configuration set value = &#039;$4&#039; where NAME IN (&#039;PS_SHOP_DOMAIN&#039;, &#039;PS_SHOP_DOMAIN_SSL&#039;)&quot;
mysql -u $targetUser $targetName -p$targetPassword -e &quot;UPDATE prstshp_shop_url SET domain = &#039;$4&#039;, domain_ssl = &#039;$4&#039; WHERE id_shop_url = 1;&quot;
sed -i s/\\^$3\\$/^$4$/g $2/.htaccess

echo Terminado.
</pre>
<p>Podría estar mejor hecho, en un lenguaje mejor o usar herramientas más avanzadas, pero es cómo me las tuve que apañar en un entorno que le faltaban muchas herramientas y comandos. Si te ha sido útil deja un comentario con tu experiencia.</p>
<p>La entrada <a href="https://blog.krusher.net/2025/05/de-como-clonar-una-instancia-de-prestashop/">De cómo clonar una instancia de Prestashop</a> se publicó primero en <a href="https://blog.krusher.net">Detrás del último no va nadie</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.krusher.net/2025/05/de-como-clonar-una-instancia-de-prestashop/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Cómo hacer café con distintos lenguajes de programación</title>
		<link>https://blog.krusher.net/2021/07/como-hacer-cafe-con-distintos-lenguajes-de-programacion/</link>
					<comments>https://blog.krusher.net/2021/07/como-hacer-cafe-con-distintos-lenguajes-de-programacion/#respond</comments>
		
		<dc:creator><![CDATA[Krusher]]></dc:creator>
		<pubDate>Fri, 30 Jul 2021 06:00:55 +0000</pubDate>
				<category><![CDATA[Coñas marineras]]></category>
		<category><![CDATA[Informática]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[asm]]></category>
		<category><![CDATA[c#]]></category>
		<category><![CDATA[café]]></category>
		<category><![CDATA[cobol]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[lisp]]></category>
		<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://blog.krusher.net/?p=2683</guid>

					<description><![CDATA[<p>El café forma parte íntimamente de la cultura de la programación, por algún motivo. Entiendo que se trata de que la cafeína estimula la concentración, aunque no me es posible corroborarlo ya que hace años que no tomo café con cafeína por el bien de mis compañeros y allegados, particularmente de mi esposa. No obstante &#8230; <a href="https://blog.krusher.net/2021/07/como-hacer-cafe-con-distintos-lenguajes-de-programacion/" class="more-link">Continuar leyendo<span class="screen-reader-text"> "Cómo hacer café con distintos lenguajes de programación"</span></a></p>
<p>La entrada <a href="https://blog.krusher.net/2021/07/como-hacer-cafe-con-distintos-lenguajes-de-programacion/">Cómo hacer café con distintos lenguajes de programación</a> se publicó primero en <a href="https://blog.krusher.net">Detrás del último no va nadie</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>El café forma parte íntimamente de la cultura de la programación, por algún motivo. Entiendo que se trata de que la cafeína estimula la concentración, aunque no me es posible corroborarlo ya que hace años que no tomo café con cafeína por el bien de mis compañeros y allegados, particularmente de mi esposa.</p>
<p><a href="https://blog.krusher.net/wp-content/uploads/2021/07/2953428679_1050cba9f9_b.jpg" data-rel="lightbox-gallery-D8N9T0JG" data-rl_title="" data-rl_caption="" title=""><img fetchpriority="high" decoding="async" class="aligncenter size-medium wp-image-2689" src="https://blog.krusher.net/wp-content/uploads/2021/07/2953428679_1050cba9f9_b-300x198.jpg" alt="" width="300" height="198" srcset="https://blog.krusher.net/wp-content/uploads/2021/07/2953428679_1050cba9f9_b-300x198.jpg 300w, https://blog.krusher.net/wp-content/uploads/2021/07/2953428679_1050cba9f9_b-768x506.jpg 768w, https://blog.krusher.net/wp-content/uploads/2021/07/2953428679_1050cba9f9_b.jpg 1000w" sizes="(max-width: 300px) 85vw, 300px" /></a></p>
<p>No obstante y como mero entretenimiento, voy a tratar de discurrir sobre cómo sería hacer café en diversos lenguajes de programación, inspiración que me ha venido de mi locuelo compinche de fechorías retroinformáticas <a href="https://twitter.com/ilgrim" target="_blank" rel="noopener">Ilgrim</a>. También del <a href="https://toggl.com/blog/kill-dragon-comic" target="_blank" rel="noopener">famosísimo comic de Toggl</a>, por supuesto.</p>
<p><span id="more-2683"></span></p>
<h4>Basic</h4>
<p>No será rápido, pero será fácil. Eso sí, no podrás personalizar demasiado el café ni ver los entresijos de la maquinaria, dado que será un café para principiantes. Perfectamente válido para consumo ocasional, aunque seguramente quieras pasarte a un café mejor en cuanto hayas aprendido lo fundamental.</p>
<h4>PASCAL</h4>
<p>Muy parecido al café Basic, aunque los botones en la cafetera tienen símbolos parecidos aunque distintos, o pueden no estar exactamente en el mismo sitio.</p>
<h4>COBOL</h4>
<p>El café en COBOL es negro y sin azúcar. Ha sido así desde que el Hombre tiene memoria y lo será hasta el final de los tiempos. Esta tradición es tan sagrada que nadie se atrevería a cuestionarla, y aunque este café está más desactualizado que llevar el jersey por los hombros y tenga pocos adeptos, siempre existirá el café COBOL.</p>
<h4>Java</h4>
<p>A menudo se critica a este café por ser lento, pero afortunadamente ha mejorado con el paso de los años. Más recientemente, se ha descrito la incomodidad de pedirlo con cuidado, ya que si alguno de los ingredientes no está disponible, el camarero te interrumpirá con un guantazo. Por ello, a veces los clientes hablan en Kotlin, un idioma para pedir café Java que evita esta burocracia.</p>
<p>Dado que es mi especialidad de café, puedo explayarme mejor. De hecho hay tres formas fundamentales de hacerlo.</p>
<p>La primera es, por supuesto, usar la cafetera que ya trae Java, importándola de java.coffee. No es mal café, pero puede que no sea justo el que necesitas o que no sea posible ponerle sacarina.</p>
<p>Por supuesto, puedes hacer tu propia cafetera usando los componentes de java.coffee. Pero mejor heredar la cafetera anterior y ponerle un asa más chula o añadir un botón para ponerle sacarina, dado que hacer café no es algo fácil y lo necesitamos para ayer.</p>
<p>La tercera, la más fácil, es simplemente diciéndole al gestor de paquetes que compre una cafetera nueva en Maven o en Gradle. Hay multitud de modelos para elegir con montones de versiones y algunas hacen un café de primera, aunque puede resultar un proceso lento dependiendo de tu casa. Parece increíble, pero la mayoría son gratuitas y traen los planos de cómo se hicieron, aunque a veces los añadidos pueden costar un extra. El uso de estas cafeteras puede variar, y es mejor asegurarse de que la toma de enchufe sea la misma que la que tienes.</p>
<p>A pesar de todo lo anterior, por desgracia, cuando llegue el momento de hacer el café, te darás cuenta que ya hay una cafetera a medio construir. Empezó siendo una hoja Excel donde se anotaban los tiempos en los que los empleados consumían café, y ahora es una fábrica de cafeteras complicadísima, sin el menor atisbo de documentación de su intrincadísima ingeniería. Hay que tener mucho cuidado con estas cafeteras, ya que son tremendamente caras, lentas, ineficientes y a veces, al cambiar algún pequeño ajuste, dejan de funcionar partes sin relación alguna.</p>
<p>El café tiene que tener un grado de dulzor, acidez, tueste y molienda perfectamente iguales a los que el cliente quiere, pero lo que ha pedido es un café indo-etrusco, y nadie tiene la menor idea de qué significa eso. El café llegará frío, tarde y saldrá un 80% más caro, y la culpa siempre será del barista de inferior salario.</p>
<h4>C#</h4>
<p>Las malas lenguas dicen que no es más que una copia del café Java. Lo cierto es que el manual de instrucciones es ligeramente distinto, con mayúsculas donde antes esperabas minúsculas, y emplea referencias elegantes para definir tareas antes comunes y aburridas.</p>
<p>Aunque no tiene la comunidad de cafeteros que tiene el café Java, es el nuevo favorito de los jugadores. Además no necesita el aparatoso transformador de corriente que puede venir o no con las cafeteras de Java.</p>
<h4>Lisp</h4>
<p>Es el café favorito de las inteligencias artificiales. El café viene servido en una taza, dentro de un vaso, dentro de un plato, dentro de una bandeja, dentro de una tabla, en una mesa colocada sobre una tarima. Se dice que este café podría tener autoconsciencia.</p>
<h3>C</h3>
<p>Es el café de los sistemas operativos, y lleva siendo así desde los años 70. Normalmente el entorno ya proporciona los granos de café, pero a veces tiene que plantarlos uno mismo, regarlos y recolectarlos. La cafetera hay que montarla desde cero, aunque es posible usar piezas que hayas encontrado en una cuneta.</p>
<p>Aunque puede ser farragoso de hacer la cafetera, el café en sí es rapidísimo, y en principio no hay problema para hacerlo en cualquier hogar, siempre que el manual de instrucciones esté en el idioma local. Hay que tener mucho ojo, eso sí: si falta algún ingrediente lo normal es que la cafetera explote. Si se toca un cable pelado se puede ir la electricidad en toda la habitación, y si la instalación eléctrica es antigua, en todo el barrio. La forma de la cafetera es totalmente permisiva, y puede que haya ingredientes cuya localización sea otra habitación, estando totalmente inaccesibles, aun así ocupando espacio.</p>
<p>Con todo, este café a menudo es citado como una de las mayores hazañas en procesos de preparación de café de la historia. Su inventor, lamentablemente, falleció el mismo día que un conocido y carismático diseñador de cafeteras de diseño carísimas, de modo que no se le recuerda como le es merecido.</p>
<h4>C++</h4>
<p>Normalmente se piensa en este café como una mejora sustancial del café <strong>C</strong>, aunque esto es objeto de debate. La gran diferencia es que este café permite hacer referencia a la taza como un objeto, y no como una estructura de partículas cerámicas abstracta. Para hacer café no es necesario juntar todas las piezas al principio del proceso, sino que puedes cogerlas según las vayas necesitando.</p>
<h4>Ensamblador</h4>
<p>El café ensamblador difícilmente se define como una manera de hacer café. Todo lo que se proporciona es una tierra baldía. Se conoce como el café más rápido posible, siempre que la cafetera sea la correcta, aunque sólo funcionará en cierto lugar de la encimera de tu cocina. También es cierto que, antes de este café, sólo existían ciertas fórmulas para preparar diversas sustancias definiendo su forma molecular, de modo que podemos considerarlo un pionero.</p>
<p>Lo primero y más llamativo con respecto a los cafés descritos anteriormente es que uno debe buscar primero minerales para hacer sus herramientas, y luego refinarlos, fundirlos y montarlos con otras herramientas. Estas herramientas serán, probablemente, para construir las herramientas que se usarán en la elaboración del café.</p>
<p>Las semillas de café deben estar perfectamente definidas. Hay que realizar todo el proceso que toma hacer café <strong>C</strong>, pero el proceso se describe con palabras muy pequeñas en frases muy muy cortas que realizan pasos diminutos. No sólo hay que regar y cuidar los cafetos, el proceso debe estar perfectamente descrito sin atisbo a malentendidos en este lenguaje minimalista. El café debe cosecharlo uno mismo, a mano o construyendo herramientas al efecto siguiendo los pasos descritos anteriormente. Incluso el abono debe proporcionarlo uno mismo, y ya sabemos lo que eso implica: para hacer este café hay que estar dispuesto a llenarse las manos de mierda.</p>
<p>La cafetera, aunque parece robusta, tiene formas espartanas. Si no fuera por las recientes mejoras en las instalaciones eléctricas domésticas, el aparato podría hacer cualquier cosa, como encender la tele o hacer sonar el timbre. (aunque esto también es aplicable café <strong>C</strong>)</p>
<p>Es, probablemente, el café más complicado que existe de hacer, y es tan específico que sólo servirá para un consumidor. Eso sí, la satisfacción será del 100% si está correctamente realizado.</p>
<h4>PHP</h4>
<p>Hacer café en PHP es de lo más sencillo, pues simplemente se le pide a la cafetera que trae incorporada que te lo prepare.</p>
<p>Aunque la teoría es sencilla, la práctica nunca es lo que parece. La cafetera tiene dos botones de encendido, pero para apagarlo hay que usar una palanca. Puede que el café que prepare sea un moka, un cappuccino, un expreso, un cortado, un americano, un vaso de agua o un sándwich de morcón. Esto depende de la versión de la cafetera, de la situación dentro de la cocina, de sus ajustes, del usuario, de la casa donde se aloje, de cómo se pida o de las fases de la luna.</p>
<p>Otro problema de la cafetera es que, en caso de que algo no funcione, puede que el mensaje esté en inglés, en italiano, en klingon, que no haya mensaje alguno, que la cafetera deje de funcionar, que sólo eche azúcar o que explote. También puede que no ocurra nada en absoluto. Todo esto depende de las condiciones escritas anteriormente descritas, las cuales pueden cambiar (y cambiarán) en cualquier momento.</p>
<h4>JavaScript</h4>
<p>Todas las semanas hay una tecnología nueva para hacer este tipo de café, que es el favorito de la gente guay. El café es precioso. El problema es que todas las cafeteras se usan de forma distinta. Al menos en los hogares domésticos es prácticamente imposible hacerse daño al usarlas.</p>
<h4>SQL, HTML, CSS, YAML&#8230;</h4>
<p>Aunque a veces se hable de ellos como cafés resulta que no lo son, son líquidos para diversos usos, como desengrasantes domésticos, líquido para circuitos de freno hidráulicos o aceites para la piel. Algunos se pueden beber sin mayores consecuencias que un dolor de estómago, otros causan la muerte instantánea.</p>
<h4>Etcétera</h4>
<p>¡Gracias por llegar hasta aquí! Como siempre, agradezco cualquier comentario aportación, corrección o insulto en la sección de comentarios, aunque advierto que, en pro del humor (o lo contrario) me he permitido la licencia de usar hipérboles o chascarrillos que no son totalmente certeros. ¡Disfrutad! (si podéis)</p>
<p>Agradecimientos a mis amigos programadores que me han asesorado para escribir esta entrada:</p>
<ul>
<li>El puntero loco</li>
<li>La función vietnamita</li>
<li>La variable glotona</li>
<li>Don Entrada y Don Salida</li>
<li>El listener sátiro</li>
<li>El paquete humano</li>
<li>Y, por último, el señor Por-qué-no-compila-esto-odio-mi-trabajo-ah-era-este-punto-y-coma.</li>
</ul>
<p>La entrada <a href="https://blog.krusher.net/2021/07/como-hacer-cafe-con-distintos-lenguajes-de-programacion/">Cómo hacer café con distintos lenguajes de programación</a> se publicó primero en <a href="https://blog.krusher.net">Detrás del último no va nadie</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.krusher.net/2021/07/como-hacer-cafe-con-distintos-lenguajes-de-programacion/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Desmontando SimCity 2000</title>
		<link>https://blog.krusher.net/2017/06/desmontando-simcity-2000/</link>
					<comments>https://blog.krusher.net/2017/06/desmontando-simcity-2000/#comments</comments>
		
		<dc:creator><![CDATA[Krusher]]></dc:creator>
		<pubDate>Fri, 09 Jun 2017 16:23:57 +0000</pubDate>
				<category><![CDATA[Informática]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[Videojuegos]]></category>
		<category><![CDATA[hack]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[mod]]></category>
		<category><![CDATA[sc2000.dat]]></category>
		<category><![CDATA[simcity 2000]]></category>
		<guid isPermaLink="false">http://blog.krusher.net/?p=1525</guid>

					<description><![CDATA[<p>SimCity 2000 (Maxis, 1993) es uno de mis juegos superfavoritos de toda la vida. Llevo jugándolo como 20 años y es parcialmente responsable de mis horrorosas notas en el instituto. Modificar juegos siempre me ha gustado, pero hasta ahora no me había puesto en serio a decodificar los datos de este simulador de ciudades. ¡Y he hallado &#8230; <a href="https://blog.krusher.net/2017/06/desmontando-simcity-2000/" class="more-link">Continuar leyendo<span class="screen-reader-text"> "Desmontando SimCity 2000"</span></a></p>
<p>La entrada <a href="https://blog.krusher.net/2017/06/desmontando-simcity-2000/">Desmontando SimCity 2000</a> se publicó primero en <a href="https://blog.krusher.net">Detrás del último no va nadie</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><strong>SimCity 2000</strong> (<strong>Maxis</strong>, 1993) es uno de mis juegos superfavoritos de toda la vida. Llevo jugándolo como 20 años y es parcialmente responsable de mis horrorosas notas en el instituto. Modificar juegos siempre me ha gustado, pero hasta ahora no me había puesto en serio a decodificar los datos de este simulador de ciudades. ¡Y he hallado varias cosas interesantes!</p>
<p>Existieron versiones en un mogollón de plataformas, desde el <strong>Macintosh</strong> (la original) hasta <strong>GameBoy Advance</strong>, pero mi favorita es la de <strong>MS-DOS</strong> y es sobre la que trata este artículo. Hay dos ficheros interesantes: el ejecutable (SC2000.EXE) y el fichero de datos (SC2000.DAT). Lamentablemente, la versión de <strong>Windows</strong> no salió en español, y la versión <strong>Network Edition</strong> que permitía juego en red funciona fatal (y también está sólo en inglés).<span id="more-1525"></span></p>
<h2>SC2000.EXE</h2>
<p>El ejecutable del juego no parece contener muchos recursos, pero sí que tiene algunos textos de la interfaz. En el editor hexadecimal se pueden ver algunas etiquetas de ancho fijo.</p>
<p><a href="https://blog.krusher.net/wp-content/uploads/2017/06/Sc2000-cadenasexe.png" data-rel="lightbox-gallery-91LmUxcf" data-rl_title="" data-rl_caption="" title=""><img decoding="async" class="aligncenter size-large wp-image-1526" src="https://blog.krusher.net/wp-content/uploads/2017/06/Sc2000-cadenasexe-1024x435.png" alt="" width="840" height="357" srcset="https://blog.krusher.net/wp-content/uploads/2017/06/Sc2000-cadenasexe-1024x435.png 1024w, https://blog.krusher.net/wp-content/uploads/2017/06/Sc2000-cadenasexe-300x127.png 300w, https://blog.krusher.net/wp-content/uploads/2017/06/Sc2000-cadenasexe-768x326.png 768w, https://blog.krusher.net/wp-content/uploads/2017/06/Sc2000-cadenasexe.png 1090w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /></a></p>
<p>También hay algunos de ancho variable que corresponden a más etiquetas de la interfaz. También parece haber algunos ficheros empotrados, en los que por ejemplo se describen los escenarios del juego.</p>
<p><a href="https://blog.krusher.net/wp-content/uploads/2017/06/Sc2000-cadenasexe2.png" data-rel="lightbox-gallery-91LmUxcf" data-rl_title="" data-rl_caption="" title=""><img decoding="async" class="aligncenter size-large wp-image-1527" src="https://blog.krusher.net/wp-content/uploads/2017/06/Sc2000-cadenasexe2-1024x435.png" alt="" width="840" height="357" srcset="https://blog.krusher.net/wp-content/uploads/2017/06/Sc2000-cadenasexe2-1024x435.png 1024w, https://blog.krusher.net/wp-content/uploads/2017/06/Sc2000-cadenasexe2-300x127.png 300w, https://blog.krusher.net/wp-content/uploads/2017/06/Sc2000-cadenasexe2-768x326.png 768w, https://blog.krusher.net/wp-content/uploads/2017/06/Sc2000-cadenasexe2.png 1090w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /></a>Por ahora desemsamblar un ejecutable de hace cerca de 25 años no está entre mis especialidades, por lo que no he averiguado gran cosa. Los punteros no son evidentes en el ejecutable, por lo que dejado ahí. Pero la chicha está en el fichero de datos.</p>
<h2>SC2000.dat</h2>
<p>Este es el principal fichero de datos del juego. No tiene cabecera, pero un vistacillo en el editor hexadecimal arroja bastantes pistas sobre su estructura.</p>
<p><a href="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000.dat_.png" data-rel="lightbox-gallery-91LmUxcf" data-rl_title="" data-rl_caption="" title=""><img loading="lazy" decoding="async" class="aligncenter size-large wp-image-1528" src="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000.dat_-1024x435.png" alt="" width="840" height="357" srcset="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000.dat_-1024x435.png 1024w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000.dat_-300x127.png 300w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000.dat_-768x326.png 768w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000.dat_.png 1090w" sizes="auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /></a></p>
<p>Resulta que desde el byte 0, lo primero que nos encontramos son bloques de 16 bytes que describen los ficheros contenidos en el paquete. El primer campo es evidente: son 12 bytes con el nombre del archivo (8 + 3 caracteres de los ficheros de MS-DOS más el punto). Si sobran caracteres, el resto de bytes son 00h.</p>
<p>Los cuatro restantes no tanto, pero resulta que el juego es original de <strong>Macintosh</strong>, que en aquella época usaba procesadores <strong>Motorola</strong>. Estos procesadores, a diferencia de los <strong>Intel</strong>, son <em>Little-Endian</em> (siendo <em>Big-Endian</em> los de <strong>Intel</strong>). Esto significa que los números de más de un byte se guardan ordenados del byte menos significativo al más significativo, en lugar del ordenamiento «natural». Se trata, pues, de un entero de 32 bits sin signo en formato <em>Little-Endian</em> en el que se codifica el <em>offset</em> (desplazamiento) del fichero cuyo nombre está justo antes.</p>
<p>La idea feliz sobre el <em>offset</em> se la debo a <a href="http://www.brettlajzer.com/36" target="_blank" rel="noopener noreferrer">Brett Lajzer</a>, un ingeniero de software de Albany que empezó antes que yo a investigar este fichero. Le escribí para intercambiar información y me advirtió sobre este punto, que no llegó a describir en su artículo.</p>
<p>El desempaquetamiento y empaquetamiento, sabido esto, resulta relativamente sencillo. He escrito un pequeño programa en Java que realiza esta operación:</p>
<pre class="brush: java; title: ; notranslate">
package sce2000;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.io.FileUtils;

public class Sce2000 {
	
	public static final String SC2000DAT = &quot;G:\\dos\\sce2000\\sc2000.dat&quot;;
	public static final int NUMFILES = 399;
	
	public static class Filestrut implements Serializable {
		private static final long serialVersionUID = 1L;
		public String filename;
		public int offset;
		public int targetOffset;
	}

	public static void main(String&#x5B;] args) throws IOException, ClassNotFoundException {
		
		if (args.length == 0) {
			info();
		} else if (&quot;x&quot;.equals(args&#x5B;0])) {
			extract();
		} else if (&quot;c&quot;.equals(args&#x5B;0])) {
			create();
		} else {
			info();
		}

	}
	
	public static void info() {
		System.out.println(&quot;Usage: x to eXtract or c to Create (after eXtract) + sc2000.dat file&quot;);
	}

	public static void create() throws IOException, ClassNotFoundException {
		
		File sc2000dat = new File(SC2000DAT);
		File metafile = new File(SC2000DAT + &quot;!/meta&quot;);
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(metafile));
		List&lt;Filestrut&gt; files = (List&lt;Filestrut&gt;) ois.readObject();
		
		List&lt;Byte&gt; targetFile = new ArrayList&lt;Byte&gt;();

		for (Filestrut file : files) {
			byte&#x5B;] fileNameBytes = file.filename.getBytes(StandardCharsets.US_ASCII);
			for (Byte myByte : fileNameBytes) {
				targetFile.add(myByte);
			}
			for (int i = 0; i &lt; 16 - fileNameBytes.length; i++) {
				targetFile.add((byte) 0);
			}
			//sourceFile.length();
		}
		
		int i = 0;
		for (Filestrut file : files) {
			byte&#x5B;] binary = Files.readAllBytes(Paths.get(SC2000DAT + &quot;!/&quot; + file.filename));
			int fileOffset = targetFile.size();
			for (Byte myByte : binary) {
				targetFile.add(myByte);
			}
			int filePointer = 12 + (16 * i);
			
			byte&#x5B;] offsetBytes = fromInt(fileOffset);
			targetFile.set(filePointer, offsetBytes&#x5B;0]);
			targetFile.set(filePointer + 1, offsetBytes&#x5B;1]);
			targetFile.set(filePointer + 2, offsetBytes&#x5B;2]);
			targetFile.set(filePointer + 3, offsetBytes&#x5B;3]);
			
			i++;
		}
		
		byte&#x5B;] binaryFile = new byte&#x5B;targetFile.size()];
		for (i = 0; i &lt; targetFile.size(); i++) {
			binaryFile&#x5B;i] = targetFile.get(i);
		}
		
		FileUtils.writeByteArrayToFile(sc2000dat, binaryFile);

		
		System.out.println();
		
	}
		

	public static void extract() throws IOException {
		
		Path sc2000dat = Paths.get(SC2000DAT);
		byte&#x5B;] data = Files.readAllBytes(sc2000dat);
		
		int vector = 0;
		List&lt;Filestrut&gt; files = new ArrayList&lt;Filestrut&gt;();
		for (int i = 0; i &lt; NUMFILES; i++) {
			
			Filestrut currfile = new Filestrut();
			
			currfile.filename =  convertFilename(Arrays.copyOfRange(data, vector, vector + 12));
			vector += 12;
			
			currfile.offset = fromByteArray(Arrays.copyOfRange(data, vector, vector + 4));
			vector += 4;
			
			System.out.println(&quot;Found file &quot; + currfile.filename + &quot; at &quot; + currfile.offset);
			
			files.add(currfile);
			
		}
		
		File dir = new File(SC2000DAT + &quot;!&quot;);
		if (dir.exists()) {
			FileUtils.deleteDirectory(dir);
		}
		
		dir.mkdir();
		
		Iterator&lt;Filestrut&gt; fileIt = files.iterator();
		
		Filestrut file = fileIt.next();
		Filestrut fileNext = null;
		
		boolean stop = false;
		while (!stop) {
			
			File extracted = new File(SC2000DAT + &quot;!/&quot; + file.filename);
			
			int init = file.offset;
			int end = -1;
			if (fileIt.hasNext()) {
				fileNext = fileIt.next();
				end = fileNext.offset;
			} else {
				end = data.length;
				stop = true;
			}

			System.out.println(&quot;Writing: &quot; + extracted.getAbsolutePath());
			FileUtils.writeByteArrayToFile(extracted, Arrays.copyOfRange(data, init, end));
			
			file = fileNext;
			
		}
		
		File metafile = new File(SC2000DAT + &quot;!/meta&quot;);
		
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(metafile));
		oos.writeObject(files);
		oos.close();
		
		System.out.println(&quot;OK!&quot;);
	}
	
	public static byte&#x5B;] fromInt(int number) {
		byte&#x5B;] bytes = ByteBuffer.allocate(4).putInt(number).array();
		swapEndianess(bytes);
		return bytes;
	}
	
	public static void swapEndianess(byte &#x5B;] bytes) {
		byte temp1 = bytes&#x5B;0];
		byte temp2 = bytes&#x5B;1];
		bytes&#x5B;0] = bytes&#x5B;3];
		bytes&#x5B;1] = bytes&#x5B;2];
		bytes&#x5B;2] = temp2;
		bytes&#x5B;3] = temp1;
	}
	
	public static int fromByteArray(byte&#x5B;] bytes) {
		// Change endianness
		swapEndianess(bytes);
		return ByteBuffer.wrap(bytes).getInt();
	}
	
	public static String convertFilename(byte&#x5B;] data) {
	    StringBuilder sb = new StringBuilder(data.length);
	    for (int i = 0; i &lt; data.length; ++ i) {
	        if (data&#x5B;i] &lt; 0) { 
	        	throw new IllegalArgumentException();
	        }
	        if (data&#x5B;i] == 0) {
	        	break;
	        }
	        sb.append((char) data&#x5B;i]);
	    }
	    return sb.toString();
	}


}
</pre>
<p>Este programa está hecho rápidamente y no está precisamente optimizado. He podido permitirme cargar o gestionar los ficheros enteros en memoria ya que el tamaño del DAT completo es de pocos megas. Además, tiene <em>hardcodeado</em> (incrustado en el código) el número de ficheros y la ruta al DAT. Mejorarlo lo dejo de deberes para el lector.</p>
<p>¿Por qué en Java? No es un lenguaje muy indicado para manejar binarios, y desde luego el no disponer de tipos sin signo no ayuda. Sencillamente lo hice en Java y no en C porque es el lenguaje con el que me gano la vida y con el que tengo más soltura, y llevo casi 8 años sin programar nada en C.</p>
<p>Usando el código anterior (o cualquier otro que el intrépido lector codifique) podemos ver los siguientes tipos de fichero:</p>
<ul>
<li>RAW, con imágenes sin cabecera.</li>
<li>PAL, con la paleta de colores que se usa en el juego.</li>
<li>Ficheros de texto, algunos TXT* sin extensión y otros con extensión RAW.</li>
<li>XMI, con la música.</li>
<li>VOC, con los efectos de sonido.</li>
<li>FNT, con las fuentes del juego.</li>
<li>Etc, etc&#8230;</li>
</ul>
<p>Hablemos un poco de cada uno.</p>
<h2>Ficheros VOC</h2>
<p>Los efectos sonoros del juego se almacenan en ficheros VOC. Bueno, ésta es fácil, ya que se trata de un formato no común pero ampliamente soportado por muchos editores de audio. Es un formato de <strong>Creative Labs</strong> que almacena principalmente audio codificado en PCM y ADPCM, aunque de forma puntual ha servido para otras codificaciones.</p>
<p><a href="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-vocs.jpg" data-rel="lightbox-gallery-91LmUxcf" data-rl_title="" data-rl_caption="" title=""><img loading="lazy" decoding="async" class="aligncenter size-large wp-image-1531" src="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-vocs-1024x560.jpg" alt="" width="840" height="459" srcset="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-vocs-1024x560.jpg 1024w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-vocs-300x164.jpg 300w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-vocs-768x420.jpg 768w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-vocs-1200x656.jpg 1200w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-vocs.jpg 1920w" sizes="auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /></a></p>
<p>Se puede abrir y guardar con <strong>Adobe Audition</strong> perfectamente. La frecuencia de muestreo varía de un fichero a otro, pero la reconoce sin problemas.</p>
<h2>Ficheros de texto</h2>
<p>Existen tres tipos de ficheros de texto. Los más sencillos son los ficheros in extensión TXT*, siendo * un número. Se pueden abrir con el <strong>Notepad++</strong> y editar sin problemas.</p>
<p><a href="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-txt.jpg" data-rel="lightbox-gallery-91LmUxcf" data-rl_title="" data-rl_caption="" title=""><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1532" src="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-txt.jpg" alt="" width="787" height="496" srcset="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-txt.jpg 787w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-txt-300x189.jpg 300w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-txt-768x484.jpg 768w" sizes="auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 984px) 61vw, (max-width: 1362px) 45vw, 600px" /></a></p>
<p>Bueno, no del todo. La codificación no es <strong>ASCII</strong>, ni <a href="https://es.wikipedia.org/wiki/Windows-1252" target="_blank" rel="noopener noreferrer">ANSI</a>, ni tampoco <strong>UNICODE</strong> porque aún no era común. ¿Cuál entonces? Pues, dado que el juego se programó para <strong>Macintosh</strong>, la codificación es <a href="https://en.wikipedia.org/wiki/Mac_OS_Roman" target="_blank" rel="noopener noreferrer">Macintosh Roman</a>. Esto es un problema porque <strong>Notepad++</strong> no da soporte a esta codificación. <a href="https://notepad-plus-plus.org/community/topic/11052/mac-encoding" target="_blank" rel="noopener noreferrer">Está pedido</a>, pero no parece prioritario, por lo que si quieres editar cómodamente hay que convertirlo antes.</p>
<p>Otros ficheros de texto son los STR*.RAW y *.RAW, siendo * un número. Su formato no es tan bonito.</p>
<p><a href="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-str.jpg" data-rel="lightbox-gallery-91LmUxcf" data-rl_title="" data-rl_caption="" title=""><img loading="lazy" decoding="async" class="aligncenter size-large wp-image-1533" src="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-str-1024x435.jpg" alt="" width="840" height="357" srcset="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-str-1024x435.jpg 1024w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-str-300x127.jpg 300w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-str-768x326.jpg 768w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-str.jpg 1090w" sizes="auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /></a></p>
<p>Al parecer el primer byte es siempre 00h, y el segundo parece almacenar el número de cadenas que hay en el texto. Las cadenas no son como las de C: en lugar de ser cadenas terminadas en 00h son cadenas precedidas de su longitud, codificada en 1 byte. Lo ideal para editar estos ficheros sería construir una utilidad al efecto, pero parece más que viable.</p>
<p>El tercer tipo de ficheros corresponde únicamente al fichero PPDT1003.RAW, que contiene una de las características más divertidas de <strong>SimCity 2000</strong>: los periódicos.</p>
<p><a href="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-periodicos.png" data-rel="lightbox-gallery-91LmUxcf" data-rl_title="" data-rl_caption="" title=""><img loading="lazy" decoding="async" class="aligncenter size-large wp-image-1534" src="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-periodicos-1024x435.png" alt="" width="840" height="357" srcset="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-periodicos-1024x435.png 1024w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-periodicos-300x127.png 300w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-periodicos-768x326.png 768w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-periodicos.png 1090w" sizes="auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /></a></p>
<p>Lamentablemente este formato es una pesadilla. Comienza con un diccionario de términos separados por 00h, y luego tiene una ristra de textos para generar los artículos. Esta codificación personalizada es de ancho variable (por ejemplo, 5C96h representa la letra ñ) y usa los términos definidos anteriormente, y naturalmente contiene marcadores de posición para los términos del artículo. Éstos se generaban proceduralmente, y un artículo que denunciaba la desaparición de un animal tenía como protagonista un gato o un rinoceronte, de la señora Dwight o del señor Martínez.</p>
<p>Antiguamente existían editores, como <a href="https://www.romhacking.net/utilities/217/" target="_blank" rel="noopener noreferrer">Thingy</a>, que daban soporte a ficheros TBL. En ellos se podía especificar un diccionario de términos (o toda una codificación) y editarlos «cómodamente». De todas formas sólo nos servirá si mantenemos el ancho de las cadenas, así que habría que descifrar el número que las precede (y su codificación) para poder editarlas como es debido. Parece bastante complicado, por lo que lo he dejado para mejor ocasión.</p>
<p><strong>Editado</strong>: Al parecer, los punteros para los textos de los periódicos están en el fichero PPDT1004.RAW. Consisten en conjuntos de 4 bytes en <em>Big-Endian</em>. Menudo lío.</p>
<h2>Ficheros de imagen</h2>
<p>Con la excepción de algunos ficheros usados para texto (descritos en el apartado anterior), los RAW son las imágenes usadas en la interfaz del juego. Los edificios y otros elementos están en unos ficheros DAT que quedan fuera del ámbito de este documento ya que se pueden editar mucho más fácilmente con el <strong>SimCity Urban Renewal Kit</strong> (SCURK), una herramienta que se incluía con versiones posteriores para editar ciudades «a manubrio» y modificar el aspecto de los edificios.</p>
<p>En lo que nos ocupa: estos ficheros RAW son mapas de bits con 8 bits por pixel (256 colores) con una cabecera de 4 bytes. No parece haber información sobre la resolución o los colores, pero para esto último existe un fichero, MINE.PAL, que especifica la paleta que usa el juego. No es un fichero de Microsoft Palette, como sugiere su extensión, sino una paleta en bruto tipo ACT. Casualmente, es el formato que usa preferentemente <strong>Adobe Photoshop</strong>.</p>
<p><a href="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-efecto2000.png" data-rel="lightbox-gallery-91LmUxcf" data-rl_title="" data-rl_caption="" title=""><img loading="lazy" decoding="async" class="aligncenter size-large wp-image-1539" src="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-efecto2000-1024x462.png" alt="" width="840" height="379" srcset="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-efecto2000-1024x462.png 1024w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-efecto2000-300x135.png 300w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-efecto2000-768x347.png 768w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-efecto2000.png 1170w" sizes="auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /></a></p>
<p>En cuanto a la resolución, no parece haber forma fácil de averiguarla simplemente mirando el archivo. Lo más sencillo es factorizar el número (menos 4 bytes) en dos múltiplos, cosa en la que el propio <strong>Photoshop</strong> nos puede asistir al abrir el archivo, al menos en versiones modernas. Por el nombre del fichero podemos más o menos discernir para qué sirve si no tenemos una idea, y si hemos jugado al juego (¡deberías!) podemos conocer más o menos su relación altura/anchura. Algunas son fáciles: la pantalla de título (TITLE.RAW) es una imagen de 640&#215;480 pixeles. Todas las imágenes usan la misma paleta, dado que el juego maneja 256 colores en todo momento.</p>
<p><a href="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-rwa.png" data-rel="lightbox-gallery-91LmUxcf" data-rl_title="" data-rl_caption="" title=""><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1538" src="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-rwa.png" alt="" width="369" height="477" srcset="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-rwa.png 369w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-rwa-232x300.png 232w" sizes="auto, (max-width: 369px) 85vw, 369px" /></a></p>
<p>Para editar los ficheros con color indexado en 8 bits siempre uso la misma técnica: las convierto a color de 24 bits + 8 de canal alfa y las edito cómodamente. Después, antes de salvarlas, las cambio a color indexado cargando la paleta del juego nuevamente. Sorprendentemente, salvando en formato <em>Adobe RAW</em> el juego las lee bien, dado que <strong>Photoshop</strong> permite conservar los 4 bytes de la cabecera. ¡Qué alivio!</p>
<p><a href="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-guardarraw.png" data-rel="lightbox-gallery-91LmUxcf" data-rl_title="" data-rl_caption="" title=""><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1540" src="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-guardarraw.png" alt="" width="946" height="653" srcset="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-guardarraw.png 946w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-guardarraw-300x207.png 300w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-guardarraw-768x530.png 768w" sizes="auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /></a></p>
<p><strong>Nota</strong>: ni que decir tiene que este formato RAW no tiene nada que ver con los formatos de datos en bruto de las cámaras digitales.</p>
<h2>Ficheros XMI</h2>
<p>En los ficheros <a href="http://www.vgmpf.com/Wiki/index.php/XMI" target="_blank" rel="noopener noreferrer">XMI</a> se almacena cada una de las músicas del juego. El formato XMI soporta varias canciones por fichero, pero no parece ser el caso. La estructura es diferente a los MIDI, pero hacen prácticamenente lo mismo: almacenar notas y eventos musicales. Se pueden reproducir en <strong>Windows</strong> cómodamente con <a href="http://www.foobar2000.org/components/view/foo_midi" target="_blank" rel="noopener noreferrer">Foobar2000</a>, y es posible <a href="http://www.vgmpf.com/Wiki/index.php/XMI#Converters" target="_blank" rel="noopener noreferrer">convertir ficheros MIDI a XMI</a> (y viceversa). <a href="https://github.com/stascorp/MIDIPLEX" target="_blank" rel="noopener noreferrer">MIDIPLEX</a> parece hacerlo desde <strong>Windows</strong>, pero no se ofrecen binarios compilados y lo hice con herramientas más antiguas para <strong>MS-DOS</strong>, usando <strong>DOSBox</strong>. De todas formas, el propio <strong>SimCity 2000</strong> necesitará <strong>DOSBox </strong>para funcionar en sistemas modernos.</p>
<p><a href="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-xmi.png" data-rel="lightbox-gallery-91LmUxcf" data-rl_title="" data-rl_caption="" title=""><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1542" src="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-xmi.png" alt="" width="642" height="427" srcset="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-xmi.png 642w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-xmi-300x200.png 300w" sizes="auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 984px) 61vw, (max-width: 1362px) 45vw, 600px" /></a></p>
<h2>Ficheros FNT</h2>
<p>Presumiblemente las tipografías del juego, al juzgar por los nombres, los números que los acompañan y la extensión. No parece que sea mismo formato de fuentes en mapa de bits FNT para <strong>Windows</strong>, por lo que no he podido abrirlo con ningún editor.</p>
<h2>Otros</h2>
<p>Otros ficheros presentes en el paquete son fuentes General Midi para chipsets OPL, diversos índices y cabeceras para los conjuntos de gráficos de edificios&#8230; nada demasiado interesante para modificar el juego. Todo lo demás parece estar incrustado en el propio ejecutable, algo que está fuera de mi alcance.</p>
<h2>En Windows</h2>
<p>Como apunte, la versión de <strong>Windows</strong> es mucho más fácil de modificar. Los recursos de imagen y sonido están presentes en formato WAV y BMP, que son mucho menos oscuros que los descritos anteriormente. El resto de recursos se encuentra incrustado en el ejecutable del juego, pero con un editor de recursos se pueden extraer y modificar sin problemas, y en formatos más accesibles que los de <strong>MS-DOS</strong>. (Yo uso <a href="http://www.angusj.com/resourcehacker/" target="_blank" rel="noopener">Resource Hacker</a>, que es gratis y funciona bastante bien)</p>
<p><a href="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-reshack.png" data-rel="lightbox-gallery-91LmUxcf" data-rl_title="" data-rl_caption="" title=""><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1541" src="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-reshack.png" alt="" width="846" height="631" srcset="https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-reshack.png 846w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-reshack-300x224.png 300w, https://blog.krusher.net/wp-content/uploads/2017/06/sc2000-reshack-768x573.png 768w" sizes="auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /></a></p>
<p>Los periódicos, sin embargo, parecen conservar el calamitoso formato de la versión de <strong>MS-DOS</strong>. Una pena porque hubiera sido interesante traducir esta versión al español.</p>
<p>Pues hasta aquí. Espero que haya sido muy educativo todo, aunque tras tanto tiempo no parece que el juego suscite mucho interés para ser modificado de ninguna forma. Siempre he querido tocar el juego y hacer mi propia versión, <em>SimCity efecto 2000</em>, un poco más gamberra, pero otra vez será.</p>
<p>La entrada <a href="https://blog.krusher.net/2017/06/desmontando-simcity-2000/">Desmontando SimCity 2000</a> se publicó primero en <a href="https://blog.krusher.net">Detrás del último no va nadie</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.krusher.net/2017/06/desmontando-simcity-2000/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Demos locas</title>
		<link>https://blog.krusher.net/2017/04/demos-locas/</link>
					<comments>https://blog.krusher.net/2017/04/demos-locas/#respond</comments>
		
		<dc:creator><![CDATA[Krusher]]></dc:creator>
		<pubDate>Wed, 26 Apr 2017 18:28:41 +0000</pubDate>
				<category><![CDATA[Informática]]></category>
		<category><![CDATA[Música]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[256]]></category>
		<category><![CDATA[64k]]></category>
		<category><![CDATA[commodore 64]]></category>
		<category><![CDATA[demo]]></category>
		<category><![CDATA[demoscene]]></category>
		<guid isPermaLink="false">http://blog.krusher.net/?p=1508</guid>

					<description><![CDATA[<p>La cultura de la demoscene jamás deja de sorprenderme. Los límites de las diferentes plataformas son superados con total alevosía, superándolos aún suponiéndolos definitivos una y otra vez. Más colores, más sonido, nuevas funciones&#8230; o simplemente hacer lo común por el camino más difícil. Per aspera ad astra. Con este pequeño artículo quisiera mostrar las demos que &#8230; <a href="https://blog.krusher.net/2017/04/demos-locas/" class="more-link">Continuar leyendo<span class="screen-reader-text"> "Demos locas"</span></a></p>
<p>La entrada <a href="https://blog.krusher.net/2017/04/demos-locas/">Demos locas</a> se publicó primero en <a href="https://blog.krusher.net">Detrás del último no va nadie</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>La cultura de la <em>demoscene</em> jamás deja de sorprenderme. Los límites de las diferentes plataformas son superados con total alevosía, superándolos aún suponiéndolos definitivos una y otra vez. Más colores, más sonido, nuevas funciones&#8230; o simplemente hacer lo común por el camino más difícil. <strong>Per aspera ad astra</strong>.</p>
<p>Con este pequeño artículo quisiera mostrar las demos que más me han sorprendido por romper las barreras que creíamos impenetrables, por hacer lo común a través de lo imposible o simplemente porque me parecen técnicamente relevantes.</p>
<p><span id="more-1508"></span></p>
<h1>The Timeless // Mercury</h1>
<p>Algunas categorías populares en la demoscene son las que limitan el tamaño del producto final (no así de la memoria o disco que pueden consumir al ejecutarse, ojo). 64k era el tamaño de la memoria de algunos ordenadores de 8bit como el <strong>Commodore 64</strong> o muchos <strong>Amstrad CPC</strong>, y ahora es una de las más reñidas en la demoscene de PC. Una de las más sonadas es <strong>The Timeles</strong>, de 2014. Puedes ver el vídeo o <a href="http://www.pouet.net/prod.php?which=62935" target="_blank" rel="noopener noreferrer">descargarla</a>.</p>
<p><iframe loading="lazy" width="840" height="473" src="https://www.youtube.com/embed/ie4u2i_5OdE?feature=oembed" frameborder="0" allowfullscreen></iframe></p>
<p>El simple hecho de entender el reto técnico que supone meter todo esto en 64k ya es ajeno a legos y principiantes. Entender cómo se hizo ya es un reto para los más avezados en la tecnología de la programación, y llegar a hacerlo es algo sólo alcanzable a los grandes maestros. Otra de las demos del grupo, <a href="https://www.youtube.com/watch?v=WBuoyegCeXI" data-rel="lightbox-video-0" target="_blank" rel="noopener noreferrer">Luma</a>, es menos espectacular visualmente pero me encanta su banda sonora. Hay <a href="http://www.pouet.net/prodlist.php?type[]=64k" target="_blank" rel="noopener noreferrer">demos en esta categoría</a> a patadas, y aún así no dejan de asombrarme. Algunas incluso <a href="https://www.youtube.com/watch?v=9rxZCKMsb0c" data-rel="lightbox-video-1" target="_blank" rel="noopener noreferrer">tienen parte cantada</a> (NSFW).</p>
<h1>8088 MPH // Hornet + CRTC + DESiRE</h1>
<p>Una de las demos más sonadas de 2015 (de la cual <a href="http://www.pixfans.com/demoanalisis-8088-mph/" target="_blank" rel="noopener noreferrer">escribí en Pixfans</a>) fue <strong>8088 MPH</strong>, que diríamos que compitió en su propia categoría. Se ejecuta sobre el <strong>IBM PC</strong> original, el modelo <strong>5150</strong> que apareció en verano de 1981. Las especificaciones donde funciona esta demo son de risa: 640 KB de RAM, un procesador <strong>8088</strong> a 4,77 MHz (y gracias, porque sólo podía leer un byte por cada ciclo de reloj) y un chipset gráfico <strong>CGA</strong> que daba 4 colores. (de unas paletas predefinidas espantosas, y a una resolución exageradamente pobre)</p>
<p><iframe loading="lazy" width="840" height="473" src="https://www.youtube.com/embed/hNRO7lno_DM?feature=oembed" frameborder="0" allowfullscreen></iframe></p>
<p>Por si hacer una demo artísticamente interesante en un hardware así no fuera suficiente, la tecnología empleada es impresionante. El sonido es muy destacable, sobre todo teniendo en cuenta que el sonido del ordenador estaba limitado a un pequeño buzzer de 1 bit. Pero lo impresionante es conseguir la cifra de 1024 colores en pantalla, siendo el primer programa en conseguirlo en <strong>CGA</strong>. Para quien esté interesado, hay un <a href="http://www.reenigne.org/blog/1k-colours-on-cga-how-its-done/" target="_blank" rel="noopener noreferrer">post técnico muy detallado</a> acerca de cómo se ha logrado tal hazaña. También hay <a href="http://8088mph.blogspot.com.es/2015/04/cga-in-1024-colors-new-mode-illustrated.html" target="_blank" rel="noopener noreferrer">otro explicado de forma gráfica</a>. Se puede <a href="http://www.pouet.net/prod.php?which=65371" target="_blank" rel="noopener noreferrer">bajar de Pouët</a>, aunque sin el hardware apropiado es muy difícil de verla correctamente, <a href="https://www.youtube.com/watch?v=Vg2n8Fp7F2g" data-rel="lightbox-video-2" target="_blank" rel="noopener noreferrer">sólo con builds muy específicos de PCem</a> es posible (y sólo parcialmente) emularla.</p>
<h1>Elevated // Rgba &amp; TBC</h1>
<p>Las demos de 64k son un logro impresionante. ¿Qué tal si subimos más aún el listón? ¿Qué tal, digamos, 4k? Este es el trabajo de unas mentes que sólo puedo calificar como brillantes. Como dicen por ahí: enséñale este vídeo a alguien aficionado a la informática y quedará más o menos indiferente. Dile lo que ocupa el programa y tendrá que verlo con sus propios ojos.</p>
<p><iframe loading="lazy" width="840" height="473" src="https://www.youtube.com/embed/jB0vBmiTr6o?feature=oembed" frameborder="0" allowfullscreen></iframe></p>
<p>Demos de 4k haylas, pero ninguna tan impresionante ni de lejos como ésta.</p>
<h1>Vicious Sid // Soundemon, Mixer, The Human Codemachine</h1>
<p><a href="https://www.youtube.com/watch?v=ZMioAPZcays" data-rel="lightbox-video-3" target="_blank" rel="noopener noreferrer">Vicious Sid</a> es una demo interesante. Visualmente no es de las más impresionantes, pero su sonido nos hace olvidar que se ejecuta en un <strong>Commodore 64</strong>. Ya con <a href="https://www.youtube.com/watch?v=Y6mXLxUZvzg" data-rel="lightbox-video-4" target="_blank" rel="noopener noreferrer">Fanta in Space</a> consiguieron lo imposible al mezclar samples digitales y sonido sintetizado con filtros y efectos, repitiendo hazaña en la presente con resultado mejores, si cabe. Pero lo verdaderamente espectacular, y que se gana a pulso el título de «intro loca» es la parte «NO SID».</p>
<p><iframe loading="lazy" width="840" height="630" src="https://www.youtube.com/embed/ZW2XKSWUPLw?feature=oembed" frameborder="0" allowfullscreen></iframe></p>
<p>No tengo constancia de que esto se haya hecho antes, ni en el <strong>Commodore 64</strong> ni en ningún otro ordenador. Sonido digital&#8230; SIN USAR EL CHIP DE AUDIO. ¿Pero qué cojones? No, el microprocesador no tiene una salida analógica <span style="text-decoration: line-through;">(caso del <strong>Z80</strong>)</span> ni existe un chip no documentado. El truco es ingenioso: programar el chip de vídeo de tal formas que sus interferencias en el circuito de audio produzcan un sonido leve, pero modelable. Completamente de locos. Completamente de genios. Se dice que el músico  <strong>Aphex Twin</strong> hizo esto con un ordenador anterior, pero hasta ahora no he podido confirmarlo.</p>
<p><strong>Editado</strong>: El Z80 no tiene una salida analógica. En <strong>Spectrum</strong> lo que se hace es una señal modulada por pulsos por el <strong>Z80</strong> y transformada a una forma de onda por el chip <strong>ULA</strong>.</p>
<h1>Robotic Liberation // PWP</h1>
<p>La demoscene parece nutrirse, de forma muy paradójica, con límites. Los límites en tamaño son relativamente nuevos, originalmente los límites eran el propio hardware. Es por eso por lo que esta demo es impresionante, ya que funciona en un ordenador <strong>VIC-20</strong>, con sólo 5120 bytes de memoria RAM disponibles para el programador, y no hablemos de la CPU, gráficos y sonido que poseía dicho computador&#8230;</p>
<p><iframe loading="lazy" width="840" height="630" src="https://www.youtube.com/embed/2SdGkkp1aq8?feature=oembed" frameborder="0" allowfullscreen></iframe></p>
<p>Esta máquina de 8 bits, anterior al <strong>Commodore 64</strong> apenas pudo articular algún que otro juego medio cojo, ha aprendido incluso a cantar (más o menos). Hasta el hardware más pequeño (literalmente) como una calculadora programable de bolsillo es capaz de hacer funcionar demos, como <a href="https://www.youtube.com/watch?v=o8GwpuMEpnc" data-rel="lightbox-video-5" target="_blank" rel="noopener noreferrer">DXM // Imation</a> funcionando en una <strong>TI-82</strong>.</p>
<h1>.kkrieger // .theprodukkt</h1>
<p>Bueno, esto no es exactamente una demo, sino un juego de PC. No obstante uno con mucha relación con la demoscene, creado por los veteranos <strong>.theprodukkt</strong>. No es particularmente jugable, su sonido espectacular y no tiene un desarrollo demasiado inspirado. ¿Qué tiene de destacable entonces? Que ocupa 96 Kb. Demencial.</p>
<p><iframe loading="lazy" width="840" height="630" src="https://www.youtube.com/embed/2NBG-sKFaB0?feature=oembed" frameborder="0" allowfullscreen></iframe></p>
<p>Hacer una demo en un tamaño ridículo es digno de un maestro. Hacer un juego como éste en 96 Kb es de locos. Por supuesto <a href="https://www.youtube.com/watch?v=2NBG-sKFaB0" data-rel="lightbox-video-6" target="_blank" rel="noopener noreferrer">puede descargarse y probarlo</a>, debería funcionar razonablemente bien en ordenadores modernos.</p>
<h1>A mind is born // Linus Åkesson</h1>
<p>La barrera de los 4k es algo ridículo. Este artículo, sin las imágenes, es bastante más grande que eso. Cuatro mil caracteres, y eso usando una codificación ASCII. Es difícil hasta imaginar que sea posible hacer demos interesantes en tan poco espacio. Pero hay quien una vez pensó: ¿y si pusiéramos un límite aún más restrictivo? ¿cómo sería hacer demos en 256 bytes? Sí, 256 bytes. Apenas dos frases.</p>
<p><iframe loading="lazy" width="840" height="630" src="https://www.youtube.com/embed/sWblpsLZ-O8?feature=oembed" frameborder="0" allowfullscreen></iframe></p>
<p>Es bastante difícil de creer. El payo, <a href="https://linusakesson.net/scene/a-mind-is-born/" target="_blank" rel="noopener noreferrer">en su página web</a>, da una explicación extensiva de cómo consiguió esta proeza, pero da igual porque no he entendido un pimiento. Hay más demos en 256 bytes, algunas bastante impresionantes, pero ninguna con este sonido. Por ahora, creo que es bastante justo decir que éste es el límite a batir. Un humilde servidor cree que no hay nada que se le parezca en este momento.</p>
<h1>Y aún hay más</h1>
<p>La cantidad de talento que se ha vertido en la demoscene es prácticamente ilimitada. Hay tantas demos buenas en cada categoría que es imposible no quedarse con algunas otras más. Si bien no creo que lleguen al nivel o a la locura de las anteriores, vale la pena echarles un vistazo. Los enlaces nos llevarán a su página de <strong>Pouët</strong>, el portal dedicado a la demoscene, en el cual hay siempre descargas y enlaces para verlo en Youtube.</p>
<ul>
<li><a href="http://www.pouet.net/prod.php?which=30244" target="_blank" rel="noopener noreferrer">debris. // farbrausch</a>: Demo de temática urbana en 177 kb que, pese a no caer en ninguna categoría, dio el pelotazo en 2007 por su excelente apartado artístico, sin igual en las demos con contenido procedural. Imprescindible.</li>
<li><a href="http://www.pouet.net/prod.php?which=99" target="_blank" rel="noopener noreferrer">State of the Art // Spaceballs</a>: Una de las demos más conocidas, y no sin motivo. Este disquette para Amiga 500 hará que creas que los 90 han vuelto. Si has sido joven en esa década su estética te va a flipar. Otra de las demos del grupo, <a href="http://www.pouet.net/prod.php?which=100" target="_blank" rel="noopener noreferrer">9 fingers</a>, si bien no es tan original es casi mejor aún.</li>
<li><a href="http://www.pouet.net/prod.php?which=25778" target="_blank" rel="noopener noreferrer">Starstruck // The Black Lotus</a>: Probablemente la demo más impresionante hecha para el chipset AGA de Amiga. No sólo impresiona su apartado artístico, también hace un uso de las 3D excelente y tiene un gran sonido.</li>
<li><a href="http://www.pouet.net/prod.php?which=63" target="_blank" rel="noopener noreferrer">Second Reality // Future Crew</a>: No necesita presentación. Su música es, probablemente, el fichero musical para tracker más oído y aplaudido, y esta veterana demo (¡1993!) sigue siendo de inspiración para muchos seguidores de la Demoscene.</li>
<li><a href="http://www.pouet.net/prod.php?which=12650" target="_blank" rel="noopener noreferrer">Blastersound BBStro // Iguana</a>: Una humilde presentación de una BBS de shareware y demos de principios de los &#8217;90. El efecto de nieve no es nuevo, y desde luego no es el mejor aprovechado, pero junto con su música para OPL2 fue bastante popular en España.</li>
<li><a href="http://www.pouet.net/prod.php?which=25783" target="_blank" rel="noopener noreferrer">1995 // Kewlers</a>: De nuevo una demo cuya tecnología no es particularmente interesante, aunque el raytracing en tiempo real siempre es de agradecer. Su música y sus efectos visuales, sin embargo, han hecho que sea una de las favoritas de los aficionados.</li>
</ul>
<p>Me he dejado muchas demos impresionantes en el tintero, así como otras categorías o sistemas que quizá no sean tan populares. Sin duda da para muchos artículos, pero hasta aquí lo que más me ha sorprendido. No sé qué depara el futuro de la demoscene, si sonido digital en una tostadora o gráficos 3D en un cepillo de dientes&#8230;</p>
<p>La entrada <a href="https://blog.krusher.net/2017/04/demos-locas/">Demos locas</a> se publicó primero en <a href="https://blog.krusher.net">Detrás del último no va nadie</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.krusher.net/2017/04/demos-locas/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Consultas MySQL con muchos JOINs</title>
		<link>https://blog.krusher.net/2016/01/consultas-mysql-con-muchos-joins/</link>
					<comments>https://blog.krusher.net/2016/01/consultas-mysql-con-muchos-joins/#respond</comments>
		
		<dc:creator><![CDATA[Krusher]]></dc:creator>
		<pubDate>Tue, 26 Jan 2016 13:03:22 +0000</pubDate>
				<category><![CDATA[Informática]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[join]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[optimización]]></category>
		<category><![CDATA[optimizer_search_depth]]></category>
		<category><![CDATA[profiling]]></category>
		<category><![CDATA[query]]></category>
		<guid isPermaLink="false">http://blog.krusher.net/?p=135</guid>

					<description><![CDATA[<p>A veces uso MySQL por elección. No por mi elección, sino por la de los clientes. 🙂 Una de las cosas que más me ha roto la cabeza es optimizar queries con muchos JOINs. Recientemente, un informe requería una consulta con 25 JOINs, y al tratar de ejecutarla nuestro servidor prácticamente lloraba que por favor &#8230; <a href="https://blog.krusher.net/2016/01/consultas-mysql-con-muchos-joins/" class="more-link">Continuar leyendo<span class="screen-reader-text"> "Consultas MySQL con muchos JOINs"</span></a></p>
<p>La entrada <a href="https://blog.krusher.net/2016/01/consultas-mysql-con-muchos-joins/">Consultas MySQL con muchos JOINs</a> se publicó primero en <a href="https://blog.krusher.net">Detrás del último no va nadie</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>A veces uso MySQL por elección. No por mi elección, sino por la de los clientes. <img src="https://s.w.org/images/core/emoji/15.1.0/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>Una de las cosas que más me ha roto la cabeza es optimizar queries con muchos JOINs. Recientemente, un informe requería una consulta con 25 JOINs, y al tratar de ejecutarla nuestro servidor prácticamente lloraba que por favor parásemos. Sin embargo, me he topado (al decir verdad, fue mi jefe) con una variable de configuración muy interesante a la hora de encarar este problema.</p>
<p><span id="more-135"></span></p>
<blockquote><p><em> optimizer_search_depth</em></p>
<p>The maximum depth of search performed by the query optimizer. Values larger than the number of relations in a query result in better query plans, but take longer to generate an execution plan for a query. Values smaller than the number of relations in a query return an execution plan quicker, but the resulting plan may be far from being optimal. If set to 0, the system automatically picks a reasonable vaue.</p></blockquote>
<p>Tan fácil como establecer esta variable de configuración a 0, las queries han pasado de tardar años a pocos segundos. En concreto, queries con unos 10 JOINs pasan de tardar 40 segundos a tardar 2. Haciendo profiling parece que casi todo el tiempo se emplea en generar un plan de ejecución pero, vaya si vale la pena. (la query original nunca conseguí que acabase, tuve que quitarle elementos para ir probándola)</p>
<p>Esta configuración se realiza tanto a nivel de sesión como de servidor, por lo que parece una buena opción a probar si hemos terminado de tirarnos de los pelos optimizando la query. Es probable que un DB admin promedio conozca estos trucos, pero a mí me sorprende lo «escondida» que está.</p>
<p>La entrada <a href="https://blog.krusher.net/2016/01/consultas-mysql-con-muchos-joins/">Consultas MySQL con muchos JOINs</a> se publicó primero en <a href="https://blog.krusher.net">Detrás del último no va nadie</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.krusher.net/2016/01/consultas-mysql-con-muchos-joins/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Webcrawler java Hoverkraft</title>
		<link>https://blog.krusher.net/2016/01/webcrawler-java-hoverkraft/</link>
					<comments>https://blog.krusher.net/2016/01/webcrawler-java-hoverkraft/#respond</comments>
		
		<dc:creator><![CDATA[Krusher]]></dc:creator>
		<pubDate>Fri, 15 Jan 2016 18:39:16 +0000</pubDate>
				<category><![CDATA[Programación]]></category>
		<category><![CDATA[hoverkraft]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[webcrawler]]></category>
		<guid isPermaLink="false">http://blog.krusher.net/?p=89</guid>

					<description><![CDATA[<p>He estado trasteando una forma de simular un navegador en Java. Hasta ahora he usado JMeter, que es tremendamente potente, configurable y para pruebas de carga es imprescindible. No obstante hay dos detalles que no me convencen: a veces uno quiere algo programático en lugar de declarativo, y segundo el JMeter es durillo de entender &#8230; <a href="https://blog.krusher.net/2016/01/webcrawler-java-hoverkraft/" class="more-link">Continuar leyendo<span class="screen-reader-text"> "Webcrawler java Hoverkraft"</span></a></p>
<p>La entrada <a href="https://blog.krusher.net/2016/01/webcrawler-java-hoverkraft/">Webcrawler java Hoverkraft</a> se publicó primero en <a href="https://blog.krusher.net">Detrás del último no va nadie</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>He estado trasteando una forma de simular un navegador en Java. Hasta ahora he usado <strong>JMeter</strong>, que es tremendamente potente, configurable y para pruebas de carga es imprescindible. No obstante hay dos detalles que no me convencen: a veces uno quiere algo programático en lugar de declarativo, y segundo el JMeter es durillo de entender y configurar. Además, no siempre es necesario tener métricas exóticas o peticiones de Ajax, a veces sólo queremos acceder a algún servicio web o analizar una web para bajar ficheros o automatizar tareas.</p>
<p>Aunque hay un montón de soluciones disponibles, me he propuesto hacer un pequeño simulador de navegador (un <em>webcrawler</em>) en Java, que permita fácilmente y de forma sencilla implementar tareas. Le he puesto a la criatura <strong>Hoverkraft</strong>. Dejo por aquí el código fuente.</p>
<p><span id="more-89"></span></p>
<pre class="brush: java; title: ; notranslate">
package net.krusher.hoverkraft;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.net.HttpCookie;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

/**
 * Hoverkraft - Das Web Boot
 * @author Axelei
 *
 */
public class Hoverkraft implements Serializable {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -4846381367781986634L;
	public static final String USER_AGENT = &quot; Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36&quot;;
	public static final String ACCEPT = &quot;text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8&quot;;
	public static final String ACCEPT_LANGUAGE = &quot;es,en-US;q=0.8,en;q=0.6&quot;;
	public static final int MAX_TRIES = 5;
	private static final String LINE_FEED = &quot;\r\n&quot;;
	
	public enum Method {
		GET, POST
	}
	
	private URL url;
	private HttpURLConnection connection;
	private String content;
	private int code = -1;
	private Method method;
	private String referer;
	private Map&lt;String, String&gt; postVars = new HashMap&lt;String, String&gt;();
	private Map&lt;String, HttpCookie&gt; cookies = new HashMap&lt;String, HttpCookie&gt;();
	private Map&lt;String, File&gt; uploads = new HashMap&lt;String, File&gt;();
	
	public Document getXml() {
		return Jsoup.parse(content);
	}
	
	public Hoverkraft() {
		super();
	}

	/**
	 * Set sail to a destination
	 * @param url
	 * @throws MalformedURLException
	 */
	public void go(String url, Method method) throws MalformedURLException {
		this.url = new URL(url);
		this.method = method;
	}
	
	public void go(String url) throws MalformedURLException {
		go(url, Method.GET);
	}
	
	public void disconnect() {
		connection.disconnect();
	}
	
	public void setPostVars(Map&lt;String, String&gt; vars) {
		this.postVars = vars;
	}
	
	public void setUploads(Map&lt;String, File&gt; uploads) {
		this.uploads = uploads;
	}
	
	/**
	 * Executes the web petition
	 * @throws IOException 
	 */
	public void execute() throws IOException {
		
		boolean redirect = false;
		int tries = 0;
		
		do {
			connection = (HttpURLConnection) url.openConnection();
			setProperties(connection);
			
			connection.connect();
			
			code = connection.getResponseCode();
			
			// Redirecciones

			if (code != HttpURLConnection.HTTP_OK) {
				if (code == HttpURLConnection.HTTP_MOVED_TEMP
					|| code == HttpURLConnection.HTTP_MOVED_PERM
					|| code == HttpURLConnection.HTTP_SEE_OTHER
					)
				redirect = true;
			}
		 
			if (redirect) {
				go(connection.getHeaderField(&quot;Location&quot;), method);
			}
		} while (redirect == true &amp;&amp; tries++ &lt; MAX_TRIES);
		
		InputStream is = (InputStream) connection.getContent();
		content = stream2string(is);
		
		referer = url.toString();

		Map&lt;String, List&lt;String&gt;&gt; headers = connection.getHeaderFields();
		
		/**
		 * Obtener cookies
		 */
		if (headers.containsKey(&quot;Set-Cookie&quot;)) {
			List&lt;String&gt; cookiesObtenidas = headers.get(&quot;Set-Cookie&quot;);
			
			for (String cookie : cookiesObtenidas) {
				List&lt;HttpCookie&gt; cookiesParseadas = HttpCookie.parse(cookie);
				for (HttpCookie cookieParseada : cookiesParseadas) {
					
					if (cookies.containsKey(cookieParseada.getName())) {
						cookies.remove(cookieParseada.getName());
					}
					cookies.put(cookieParseada.getName(), cookieParseada);
				}
			}
		}
	}
	
	private void setProperties(HttpURLConnection connection) throws IOException {
		
		// Cabeceras
		connection.setRequestProperty(&quot;user-agent&quot;, USER_AGENT);
		connection.setRequestProperty(&quot;accept&quot;, ACCEPT);
		connection.setRequestProperty(&quot;accept-language&quot;, ACCEPT_LANGUAGE);
		if (referer != null) {
			connection.setRequestProperty(&quot;referer&quot;, referer);
		}
		connection.setRequestMethod(method.toString());
		
		// Cookies
		for (HttpCookie cookie : cookies.values()) {
			connection.setRequestProperty(&quot;Cookie&quot;, cookie.toString());
		}
		
		connection.setDoOutput(false);
		
		// Variables Post y demÃ¡s
		if (method == Method.POST &amp;&amp; !postVars.isEmpty() &amp;&amp; uploads.isEmpty()) {
			
			connection.setDoOutput(true);
			StringBuffer urlParameters = new StringBuffer();
			
			for (Entry&lt;String, String&gt; var : postVars.entrySet()) {
				urlParameters.append(URLEncoder.encode(var.getKey(), &quot;UTF-8&quot;) + &quot;=&quot; + URLEncoder.encode(var.getValue(), &quot;UTF-8&quot;) + &quot;&amp;&quot;);
			}
			
			if (urlParameters.charAt(urlParameters.length() - 1) == '&amp;') {
				urlParameters.deleteCharAt(urlParameters.length() - 1);
			}
			postVars.clear();
			
			connection.setRequestProperty(&quot;Content-Type&quot;, &quot;application/x-www-form-urlencoded&quot;); 
			connection.setRequestProperty(&quot;charset&quot;, &quot;utf-8&quot;);
			connection.setRequestProperty(&quot;Content-Length&quot;, Integer.toString(urlParameters.toString().getBytes().length));
			
			DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
			wr.writeBytes(urlParameters.toString());
			wr.flush();
			wr.close();
		}
		
		if (method == Method.POST &amp;&amp; !uploads.isEmpty()) {

			String boundary = &quot;===&quot; + System.currentTimeMillis() + &quot;===&quot;;

			connection.setUseCaches(false);
			connection.setDoOutput(true);
			connection.setDoInput(true);

			connection.setRequestProperty(&quot;Content-Type&quot;, &quot;multipart/form-data; boundary=&quot; + boundary);
			connection.setRequestProperty(&quot;charset&quot;, &quot;UTF-8&quot;);
			
			OutputStream outputStream = connection.getOutputStream();
			PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, &quot;UTF-8&quot;), true);

			for (Entry&lt;String, String&gt; var : postVars.entrySet()) {

				writer.append(&quot;--&quot; + boundary).append(LINE_FEED);
		        writer.append(&quot;Content-Disposition: form-data; name=\&quot;&quot; + var.getKey() + &quot;\&quot;&quot;).append(LINE_FEED);
		        writer.append(&quot;Content-Type: text/plain; charset=UTF-8&quot;).append(LINE_FEED);
		        writer.append(LINE_FEED);
		        writer.append(var.getValue()).append(LINE_FEED);
		        writer.flush();
			}
			
			for (Entry&lt;String, File&gt; fichero : uploads.entrySet()) {
		        String fileName = fichero.getValue().getName();
		        writer.append(&quot;--&quot; + boundary).append(LINE_FEED);
		        writer.append(&quot;Content-Disposition: form-data; name=\&quot;&quot; + fichero.getKey() + &quot;\&quot;; filename=\&quot;&quot; + fileName + &quot;\&quot;&quot;).append(LINE_FEED);
		        writer.append(&quot;Content-Type: &quot; + URLConnection.guessContentTypeFromName(fileName)).append(LINE_FEED);
		        writer.append(&quot;Content-Transfer-Encoding: binary&quot;).append(LINE_FEED);
		        writer.append(LINE_FEED);
		        writer.flush();
		        
		        FileInputStream inputStream = new FileInputStream(fichero.getValue());
		        byte&#x5B;] buffer = new byte&#x5B;4096];
		        int bytesRead = -1;
		        while ((bytesRead = inputStream.read(buffer)) != -1) {
		            outputStream.write(buffer, 0, bytesRead);
		        }
		        outputStream.flush();
		        inputStream.close();
		         
		        writer.append(LINE_FEED);
		        writer.flush();   
			}
			
			writer.append(LINE_FEED).flush();
	        writer.append(&quot;--&quot; + boundary + &quot;--&quot;).append(LINE_FEED);
	        writer.close();

			postVars.clear();
			uploads.clear();
			
		}

		
	}
	
	/**
	 * Resets the browser
	 */
	public void reset() {
		url = null;
		connection = null;
		content = null;
		code = -1;
		referer = null;
		postVars.clear();
		cookies.clear();
		uploads.clear();
	}
	
	/**
	 * Get contents of last execution
	 * @return
	 */
	public String getContent() {
		return content;
	}
	
	/**
	 * Gets HTTP code of last execution
	 * @return
	 */
	public int getCode() {
		return code;
	}
	
	private static String stream2string(InputStream is) {
		String salida = &quot;&quot;;
		Scanner scanner = new Scanner(is);
		scanner.useDelimiter(&quot;\\A&quot;);
		while (scanner.hasNext()) {
			salida += scanner.next();
		}
		scanner.close();
		return salida;
	}
}
</pre>
<p>Este código depende de la librería <strong>jsoup</strong>, que naturalmente está disponible como software libre por ahí y funciona desde Maven perfectamente.</p>
<p>Por supuesto estaría encantado de poder leer cualquier mejora o crítica. ¡Comenta, comenta!</p>
<p>La entrada <a href="https://blog.krusher.net/2016/01/webcrawler-java-hoverkraft/">Webcrawler java Hoverkraft</a> se publicó primero en <a href="https://blog.krusher.net">Detrás del último no va nadie</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.krusher.net/2016/01/webcrawler-java-hoverkraft/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Converter JSF para SelectOneMenu</title>
		<link>https://blog.krusher.net/2016/01/converter-jsf-para-selectonemenu/</link>
					<comments>https://blog.krusher.net/2016/01/converter-jsf-para-selectonemenu/#comments</comments>
		
		<dc:creator><![CDATA[Krusher]]></dc:creator>
		<pubDate>Fri, 15 Jan 2016 12:04:30 +0000</pubDate>
				<category><![CDATA[Programación]]></category>
		<category><![CDATA[converter]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[JSF]]></category>
		<guid isPermaLink="false">http://blog.krusher.net/?p=74</guid>

					<description><![CDATA[<p>La principal característica de JSF (o, al menos, la que más me gusta) es la facilidad para enlazar atributos del bean controlador desde la vista xhtml. No obstante existe una limitación importante: en el estándar HTTP las claves y valores siempre serán cadenas porque así es como se transmiten. Sí, se puede serializar el objeto en &#8230; <a href="https://blog.krusher.net/2016/01/converter-jsf-para-selectonemenu/" class="more-link">Continuar leyendo<span class="screen-reader-text"> "Converter JSF para SelectOneMenu"</span></a></p>
<p>La entrada <a href="https://blog.krusher.net/2016/01/converter-jsf-para-selectonemenu/">Converter JSF para SelectOneMenu</a> se publicó primero en <a href="https://blog.krusher.net">Detrás del último no va nadie</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>La principal característica de <strong>JSF</strong> (o, al menos, la que más me gusta) es la facilidad para enlazar atributos del bean controlador desde la vista xhtml. No obstante existe una limitación importante: en el estándar HTTP las claves y valores siempre serán cadenas porque así es como se transmiten. Sí, se puede serializar el objeto en base64, pero en casi cualquier circunstancia se debe huir de una salvajada así.</p>
<p>¿Cómo hacer, pues, que el valor de un control se enlace directamente con un objeto? Pues JSF provee para ello los <strong>converters</strong>. Bueno, permite que tú los programes, claro. Tampoco <a href="http://www.primefaces.org/" target="_blank">PrimeFaces</a>, que es mi librería de componentes de eleción, incluye estos conversores. Así pues, he programado uno pequeño para los <a href="http://www.primefaces.org/showcase/ui/input/oneMenu.xhtml" target="_blank">SelectOneMenu</a>, los menús desplegables asemejables a comboboxes. Lo dejo aquí para referencia mía y por si puede servirle de algo a alguien.</p>
<p><span id="more-74"></span></p>
<pre class="brush: java; title: ; notranslate">
/**
 * The Class SelectOneMenuConverter.
 */
@FacesConverter(&quot;selectOneMenuConverter&quot;)
public class SelectOneMenuConverter implements Converter {

	@Override
	public Object getAsObject(final FacesContext arg0, final UIComponent arg1, final String objectString) {
		if (objectString == null) {
			return null;
		}

		return fromSelect(arg1, objectString);
	}

	/**
	 * Serialize.
	 *
	 * @param object
	 *            the object
	 * @return the string
	 */
	private String serialize(final Object object) {
		if (object == null) {
			return null;
		}
		return object.getClass() + &quot;@&quot; + object.hashCode();
	}

	/**
	 * From select.
	 *
	 * @param currentcomponent
	 *            the currentcomponent
	 * @param objectString
	 *            the object string
	 * @return the object
	 */
	private Object fromSelect(final UIComponent currentcomponent, final String objectString) {

		if (currentcomponent.getClass() == UISelectItem.class) {
			final UISelectItem item = (UISelectItem) currentcomponent;
			final Object value = item.getValue();
			if (objectString.equals(serialize(value))) {
				return value;
			}
		}

		if (currentcomponent.getClass() == UISelectItems.class) {
			final UISelectItems items = (UISelectItems) currentcomponent;
			final List&amp;lt;Object&amp;gt; elements = (List&amp;lt;Object&amp;gt;) items.getValue();
			for (final Object element : elements) {
				if (objectString.equals(serialize(element))) {
					return element;
				}
			}
		}


		if (!currentcomponent.getChildren().isEmpty()) {
			for (final UIComponent component : currentcomponent.getChildren()) {
				final Object result = fromSelect(component, objectString);
				if (result != null) {
					return result;
				}
			}
		}
		return null;
	}

	@Override
	public String getAsString(final FacesContext arg0, final UIComponent arg1, final Object object) {
		return serialize(object);
	}

}
</pre>
<p>Básicamente, convierte cualquier objeto en una cadena codificada con el nombre de la clase y el hash. A la hora de recuperarlo, accede a la colección enlazada al valor de nuestro desplegable para buscar el objeto adecuado y así devolverlo. Ciertamente depende de que el método hashCode esté bien codificado y devuelva (casi con toda seguridad) valores distintos para objetos distintos, pero es una forma rápida de hacerlo y que funcionaría en casi toda circunstancia. Usarlo es tan fácil como:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;p:selectOneMenu id=&quot;utiSelector&quot; converter=&quot;selectOneMenuConverter&quot; value=&quot;#{miBean.miObjeto}&quot;&gt;
	&lt;f:selectItem itemValue=&quot;#{null}&quot; noSelectionOption=&quot;true&quot; /&gt;
	&lt;f:selectItems value=&quot;#{miBean.listaObjetos}&quot; var=&quot;objeto&quot; itemValue=&quot;#{objeto}&quot; itemLabel=&quot;#{objeto.etiqueta}&quot; /&gt;
&lt;/p:selectOneMenu&gt;
</pre>
<p>Algo que suelo hacer en los DTO o entidades es que el hashcode sea la clave, o el hashcode de ésta si no es numérica. Pero eso ya es otra historia.</p>
<p>La entrada <a href="https://blog.krusher.net/2016/01/converter-jsf-para-selectonemenu/">Converter JSF para SelectOneMenu</a> se publicó primero en <a href="https://blog.krusher.net">Detrás del último no va nadie</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.krusher.net/2016/01/converter-jsf-para-selectonemenu/feed/</wfw:commentRss>
			<slash:comments>15</slash:comments>
		
		
			</item>
	</channel>
</rss>
