<?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>java archivos - Detrás del último no va nadie</title>
	<atom:link href="https://blog.krusher.net/tag/java/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>Porque alguien tenía que pensar en los peces</description>
	<lastBuildDate>Fri, 30 Jul 2021 15:22:23 +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>java archivos - Detrás del último no va nadie</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<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-xfPHkgKV" 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-fi9WCjBv" 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-fi9WCjBv" 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-fi9WCjBv" 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-fi9WCjBv" 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-fi9WCjBv" 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-fi9WCjBv" 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-fi9WCjBv" 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-fi9WCjBv" 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-fi9WCjBv" 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-fi9WCjBv" 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-fi9WCjBv" 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-fi9WCjBv" 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>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>
