Nortis (anciennement Notris) est un jeu PSX homebrew, écrit en C à l'aide d'outils modernes. Il est entièrement jouable sur le matériel d'origine et est alimenté par PSNoobSDK.
Consultez la base de code PSX ici.
L'année dernière, j'ai mis la main sur une PlayStation 1 noire et rare. Elle s'appelle Net Yaroze et est une console spéciale qui peut jouer à des jeux homebrew ainsi qu'à des titres PSX ordinaires. Cela faisait partie d'un projet spécial de Sony visant à attirer les amateurs et les étudiants dans l'industrie du jeu vidéo.
Les jeux Yaroze étaient très limités, car Sony ne voulait pas que les codeurs de chambre rivalisent avec les développeurs commerciaux. Ils ne pouvaient être lus que sur d'autres Yarozes ou sur des disques de démonstration spéciaux. Ils devaient tenir entièrement dans la RAM du système sans accès au CD-ROM. Malgré ces limitations, Yaroze a favorisé une communauté passionnée de développeurs indépendants.
Et maintenant j'avais le mien. Ce qui m’a fait réfléchir : comment c’était réellement d’écrire un jeu PlayStation ?
Il s'agit de la façon dont j'ai écrit moi-même un simple jeu homebrew PSX, en utilisant une version open source des bibliothèques mais toujours fonctionnant sur le matériel d'origine et écrit en C classique.
Passer cette section
Les jeux PSX étaient généralement écrits en C sur les postes de travail Windows 9X. Le kit de développement officiel était une paire de cartes d'extension ISA insérées dans une carte mère IBM PC commune et contenant l'intégralité du chipset du système PSX, la sortie vidéo et de la RAM supplémentaire (8 Mo au lieu de 2 Mo). Cela fournissait une sortie TTY et du débogueur à la machine hôte.
Vous avez peut-être entendu parler des PlayStation bleues. Ceux-ci étaient destinés à l'assurance qualité plutôt qu'au développement et sont identiques aux unités de vente au détail, sauf qu'ils peuvent lire des CD-ROM gravés. Cependant, au moins une entreprise a vendu un module complémentaire spécial pour les convertir en devkits :
La conception était très conviviale pour les développeurs. Vous pouvez jouer à votre jeu sur CRT avec des contrôleurs normaux tout en parcourant les points d'arrêt GDB sur votre PC Windows 95, en feuilletant un épais manuel de fonctions du SDK C.
En principe, un développeur PSX pourrait travailler entièrement en C. Le SDK comprenait un ensemble de bibliothèques C appelé PSY-Q, ainsi qu'un programme de compilation ccpsx
qui n'était en réalité qu'une interface sur GCC. Cela prenait en charge une gamme d'optimisations, telles que l'insertion de code et le déroulement de boucles, bien que les sections critiques en termes de performances justifiaient toujours un assemblage optimisé manuellement.
(Vous pouvez en savoir plus sur ces optimisations dans ces diapositives de la conférence SCEE).
C++ était pris en charge par ccpsx
mais avait la réputation de générer du code « gonflé », ainsi que des temps de compilation plus lents. En réalité, le C était la lingua franca du développement PSX, mais certains projets utilisaient des langages de script dynamiques au-dessus d'un moteur de base. Par exemple, Metal Gear Solid a utilisé TCL pour les scripts de niveau ; et les jeux Final Fantasy sont allés plus loin et ont implémenté leurs propres langages de bytecode pour les batailles, les systèmes de terrain et de mini-jeux. (Vous pouvez en savoir plus à ce sujet ici).
( Pour en savoir plus, jetez un œil à https://www.retroreversing.com/official-playStation-devkit )
Passer cette section
Mais j'y suis arrivé d'un point de vue très différent : un ingénieur logiciel en 2024 qui travaillait principalement sur des applications Web. Mon expérience professionnelle s'était presque exclusivement concentrée sur des langages de haut niveau comme JavaScript et Haskell ; J'avais fait un peu de travail sur OpenGL et C++, mais le C++ moderne est presque un langage complètement différent du C.
Je savais que les SDK PSX existaient pour des langages comme Rust, mais je voulais découvrir la « vraie » programmation PSX, comme cela avait été fait dans les années 90. Il s'agirait donc de chaînes d'outils modernes et de bibliothèques open source, mais en C jusqu'au bout.
Le jeu devait être quelque chose en 2D qui pourrait être prototypé en quelques jours. J'ai opté pour un clone de Tetris – j'ai pensé que ce serait suffisamment complexe pour faire l'expérience de ce que je voulais.
La première étape consistait à construire un prototype utilisant une technologie familière. Cela me permettrait de définir la conception de base, puis la logique pourrait être traduite au coup par coup en C.
En tant que développeur Web, la technologie de prototypage la plus évidente était JavaScript : elle est simple, concise, facile à déboguer et elle intègre l'API graphique HTML5 <canvas>
. Les choses se sont mises en place très rapidement
Dans le même temps, je craignais que des fonctionnalités JavaScript de plus haut niveau ne soient difficiles à porter. Tout ce qui utilise des classes ou des fermetures devrait être complètement réécrit, j'ai donc pris soin de me limiter à un sous-ensemble simple et procédural du langage.
Maintenant, j'avais en fait une arrière-pensée en me lançant dans ce projet : c'était une excuse pour enfin apprendre le C. Le langage occupait une place importante dans mon esprit et j'avais commencé à développer un complexe d'infériorité de ne pas le connaître.
C a une réputation intimidante et je craignais les histoires d'horreur de pointeurs pendants, de lectures mal alignées et du redoutable segmentation fault
. Plus précisément : j'avais peur que si j'essayais d'apprendre le C et que j'échouais, je découvrirais que je n'étais pas vraiment un très bon programmeur après tout.
Pour simplifier les choses, j'ai pensé que je pourrais utiliser SDL2 pour gérer les entrées et les graphiques, et compiler pour mon environnement de bureau (MacOS). Cela me donnerait un cycle de construction/débogage rapide et rendrait la courbe d'apprentissage aussi douce que possible.
Malgré mes craintes, j'ai trouvé C incroyablement amusant. Très vite, ça a « cliqué » pour moi. Vous partez de primitives très simples - structures, caractères, fonctions - et les construisez en couches d'abstraction pour finalement vous retrouver au sommet d'un système de travail entier.
Le portage du jeu n'a pris que quelques jours et j'ai été très satisfait de mon premier vrai projet C. Et je n'avais pas eu un seul segfault !
Travailler avec SDL a été un plaisir, mais certains aspects m'obligeaient à allouer de la mémoire de manière dynamique. Ce serait un non-non sur PlayStation, où le malloc
fourni par le noyau PSX ne fonctionne pas correctement. Et le pipeline graphique constituerait un pas encore plus grand...
En ce qui concerne les homebrews PlayStation, il existe deux choix principaux pour votre SDK. Soit:
Il existe quelques autres options comme le C++ Psy-Qo , et vous pouvez même renoncer à n'importe quel SDK juste pour effectuer vous-même des E/S mappées en mémoire - mais je n'ai pas été assez courageux pour cela.
Le plus gros problème avec Psy-Q est qu'il s'agit toujours d'un code propriétaire de Sony, même 30 ans plus tard. Légalement, tout homebrew construit avec celui-ci est en danger. C'est ce qui a fait couler le démake de Portal64 : il a lié statiquement libultra
, qui est le SDK N64 exclusif de Nintendo.
Mais pour être honnête, la principale raison pour laquelle j'ai choisi PSNoobSDK était qu'il était très bien documenté et simple à configurer. L'API est très similaire à Psy-Q : en fait, pour de nombreuses fonctions, je pouvais simplement consulter les références imprimées fournies avec mon Yaroze.
Si l'utilisation d'un SDK non authentique offense le puriste PSX qui sommeille en vous, n'hésitez pas à arrêter de lire maintenant avec dégoût.
Ma première tâche était une sorte de bonjour tout le monde : deux carrés sur un fond coloré. Cela semble simple, non ?
Passer cette section
(*Certains de ces éléments sont simplifiés. Pour un guide plus fiable, lisez le didacticiel PSNoobSDK)
Pour commencer, considérez la PSX VRAM comme un grand canevas de 1024 x 512 de pixels de 16 bits. Au total, cela fait 1 Mo de mémoire partagée par les framebuffers et les textures. On peut choisir la résolution du framebuffer de sortie - même jusqu'à 640x480 pixels si on est gourmand - mais plus de résolution = moins de textures.
La plupart des jeux PSOne (et... les jeux en général) ont une notion de rendu dual-buffer : pendant qu'une image est en préparation, l'autre est envoyée à l'écran. Nous devons donc allouer deux frame buffers :
(Vous pouvez maintenant comprendre pourquoi le 640x480 n'est pas pratique : il n'y a pas assez d'espace pour deux tampons 480p. Mais ce mode PEUT être utilisé par des éléments comme le logo de démarrage PSX, qui ne nécessite pas beaucoup d'animation)
Les tampons (appelés alternativement environnements d'affichage et de dessin) sont échangés à chaque image. La plupart des jeux PSX ciblent 30 images par seconde (en Amérique du Nord), mais l'interruption VSync réelle se produit à 60 Hz. Certains jeux parviennent à fonctionner à 60 images par seconde - on pense à Tekken 3 et Kula World (Roll Away) - mais vous devez évidemment effectuer le rendu en deux fois moins de temps. N'oubliez pas que nous ne disposons que de 33 MHz de puissance de traitement.
Mais comment fonctionne le processus de dessin ? Cela est fait par le GPU, mais le GPU PSX fonctionne très différemment d'une carte graphique moderne. Essentiellement, à chaque image, le GPU reçoit une liste ordonnée de « paquets » ou de commandes graphiques. "Dessinez un triangle ici", "chargez cette texture pour peaufiner le quad suivant", et cetera.
Le GPU n'effectue pas de transformations 3D ; c'est le travail du coprocesseur GTE (Geometry Transform Engine). Les commandes GPU représentent des graphiques purement 2D, déjà manipulés par du matériel 3D.
Cela signifie que le chemin d'un pixel PSX est le suivant :
Donc, en pseudocode, la boucle de trame PSX (en gros) se déroule comme ceci
FrameBuffer [0, 1]
OrderingTable [0, 1]
id = 1 // flips every frame
loop {
// Game logic
// Construct the next screen by populating the current ordering table
MakeGraphics(OrderingTable[id])
// Wait for last draw to finish; wait for vertical blank
DrawSync()
VSync()
// The other frame has finished drawing in background, so display it
SetDisplay(Framebuffer[!id])
// Start drawing current frame
SetDrawing(Framebuffer[id])
// Send ordering table contents to GPU via DMA
Transfer(OrderingTable[id])
// Flip
id = !id
}
Vous pouvez voir que pendant que l'image 1 est à l'écran, l'image 2 est toujours en cours de peinture et l'image 3 est potentiellement encore « construite » par le programme lui-même. Ensuite, après DrawSync / VSync, nous envoyons l'image 2 au téléviseur et obtenons l'image de dessin GPU 3.
Comme mentionné, le GPU est un élément matériel entièrement 2D, il ne connaît pas les coordonnées z dans l'espace 3D. Il n'y a pas de "z-buffer" pour décrire les occlusions, c'est-à-dire quels objets se trouvent devant les autres. Alors, comment les objets sont-ils triés devant les autres ?
La façon dont cela fonctionne est que la table de classement comprend une chaîne de commandes graphiques à liaison inverse. Ceux-ci sont parcourus de l'arrière vers l'avant pour implémenter l' algorithme du peintre .
Pour être précis, le tableau de tri est une liste à liens inversés. Chaque élément a un pointeur vers l'élément précédent dans la liste, et nous ajoutons des primitives en les insérant dans la chaîne. Généralement, les OT sont initialisés sous la forme d'un tableau fixe, chaque élément du tableau représentant un « niveau » ou une couche dans l'affichage. Les OT peuvent être imbriqués pour implémenter des scènes complexes.
Le diagramme suivant permet de l'expliquer (source)
Cette approche n'est pas parfaite et parfois la géométrie PSX montre un découpage étrange, car chaque poly ne peut être qu'à un seul « indice z » dans l'espace de l'écran, mais cela fonctionne assez bien pour la plupart des jeux. De nos jours, de telles limitations sont considérées comme faisant partie du charme distinctif de la PSX.
Passer cette section
Nous avons beaucoup parlé de théorie – à quoi cela ressemble-t-il dans la pratique ?
Cette section ne passera pas en revue tout le code ligne par ligne mais devrait vous donner un avant-goût des concepts graphiques PSX. Si vous voulez voir le code complet, allez sur hello-psx/main.c
.
Alternativement, si vous n'êtes pas un codeur, n'hésitez pas à aller de l'avant. C'est juste pour les techniciens curieux.
La première chose dont nous avons besoin, ce sont des structures pour contenir nos tampons. Nous aurons un RenderContext
qui contient deux RenderBuffers
, et chaque RenderBuffer
contiendra :
displayEnv
(spécifie la zone VRAM du tampon d'affichage actuel)drawEnv
(spécifie la zone VRAM du tampon de dessin actuel)orderingTable
(liste chaînée inversée qui contiendra des pointeurs vers des paquets graphiques)primitivesBuffer
(structs pour les paquets/commandes graphiques - incluant tous les polygones) #define OT_SIZE 16
#define PACKETS_SIZE 20480
typedef struct {
DISPENV displayEnv ;
DRAWENV drawEnv ;
uint32_t orderingTable [ OT_SIZE ];
uint8_t primitivesBuffer [ PACKETS_SIZE ];
} RenderBuffer ;
typedef struct {
int bufferID ;
uint8_t * p_primitive ; // next primitive
RenderBuffer buffers [ 2 ];
} RenderContext ;
static RenderContext ctx = { 0 };
Pour chaque image, nous inverserons le bufferID
ce qui signifie que nous pouvons travailler de manière transparente sur une image pendant que l'autre est affichée. Un détail clé est que le p_primitive
est constamment pointé vers l'octet suivant dans le primitivesBuffer
actuel. Il est impératif que cela soit incrémenté à chaque fois qu'une primitive est allouée et réinitialisée à la fin de chaque trame.
Avant tout, nous devons configurer nos environnements d'affichage et de dessin, en configuration inverse pour que DISP_ENV_1
utilise la même VRAM que DRAW_ENV_0
, et vice versa.
// x y width height
SetDefDispEnv ( DISP_ENV_0 , 0 , 0 , 320 , 240 );
SetDefDispEnv ( DISP_ENV_1 , 0 , 240 , 320 , 240 );
SetDefDrawEnv ( DRAW_ENV_0 , 0 , 240 , 320 , 240 );
SetDefDrawEnv ( DRAW_ENV_1 , 0 , 0 , 320 , 240 );
Je suis assez condensé ici - mais à partir de là, chaque image ressemble essentiellement à
while ( 1 ) {
// do game stuff... create graphics for next frame...
// at the end of loop body
// wait for drawing to finish, wait for next vblank interval
DrawSync ( 0 );
VSync ( 0 );
DISPENV * p_dispenv = & ( ctx . buffers [ ctx . bufferID ]. displayEnv );
DRAWENV * p_drawenv = & ( ctx . buffers [ ctx . bufferID ]. drawEnv );
uint32_t * p_ordertable = ctx . buffers [ ctx . bufferID ]. orderingTable ;
// Set display and draw environments
PutDispEnv ( p_dispenv );
PutDrawEnv ( p_drawenv );
// Send ordering table commands to GPU via DMA, starting from the end of the table
DrawOTagEnv ( p_ordertable + OT_SIZE - 1 , p_drawEnv );
// Swap buffers and clear state for next frame
ctx . bufferID ^= 1 ;
ctx . p_primitive = ctx . buffers [ ctx . bufferID ]. primitivesBuffer ;
ClearOTagR ( ctx . buffers [ 0 ]. orderingTable , OT_SIZE );
}
Cela pourrait être beaucoup à prendre en compte. Ne vous inquiétez pas.
Si vous voulez vraiment comprendre cela, le mieux est de jeter un œil à hello-psx/main.c
. Tout est commenté avec beaucoup de détails. Vous pouvez également consulter le didacticiel PSNoobSDK... il est assez concis et écrit assez clairement.
Maintenant... comment dessine-t-on des trucs ? Nous écrivons des structures dans notre tampon de primitives. Ce tampon est tapé comme une simple grande liste de chars
, nous le transposons donc dans notre structure de forme/commande, puis avançons le pointeur du tampon des primitives en utilisant sizeof
:
// Create a tile primitive in the primitive buffer
// We cast p_primitive as a TILE*, so that its char used as the head of the TILE struct
TILE * p_tile = ( TILE * ) p_primitive ;
setTile ( p_tile ); // very very important to call this macro
setXY0 ( p_tile , x , y );
setWH ( p_tile , width , width );
setRGB0 ( p_tile , 252 , 32 , 3 );
// Link into ordering table (z level 2)
int z = 2 ;
addPrim ( ordering_table [ buffer_id ] + z , p_primitive );
// Then advance buffer
ctx . p_primitive += sizeof ( TILE );
Nous venons d'insérer un carré jaune ! ? Essayez de contenir votre enthousiasme.
Passer cette section
À ce stade de mon parcours, tout ce que j'avais était un programme de démonstration "hello world", avec des graphiques de base et une entrée de contrôleur. Vous pouvez voir dans le code de hello-psx
que je documentais autant que possible, vraiment pour mon propre bénéfice. Un programme de travail était une étape positive mais pas un véritable jeu.
Il était temps de devenir réel .
Notre jeu doit montrer le score.
La PSX ne vous offre pas vraiment grand-chose en termes de rendu de texte. Il existe une police de débogage (illustré ci-dessus) mais elle est extrêmement basique - pour le développement et pas grand-chose d'autre.
Au lieu de cela, nous devons créer une texture de police et l'utiliser pour peaufiner les quads. J'ai créé une police monospace avec https://www.piskelapp.com/ et l'ai exportée au format PNG transparent :
Les textures PSX sont stockées dans un format appelé TIM. Chaque fichier TIM comprend :
Étant donné que l'emplacement VRAM de la texture est « intégré » au fichier TIM, vous avez besoin d'un outil pour gérer vos emplacements de texture. Je recommande https://github.com/Lameguy64/TIMedit pour cela.
À partir de là, nous avons juste une fonction pour peaufiner un tas de quads, avec les décalages UV basés sur chaque valeur ASCII.
Nous avons besoin d’un espace dans lequel les pièces peuvent s’insérer. Il serait facile d'utiliser un rectangle blanc ennuyeux pour cela, mais je voulais quelque chose qui ressemble plus... PlayStation
Notre interface utilisateur se met en place. Et les pièces ?
Vient maintenant une conception visuelle importante. Idéalement, chaque brique devrait être visuellement distincte avec des bords nets et ombrés. On fait cela avec deux triangles et un quad :
À une résolution native de 1x, l'effet serait moins clair, mais il semble toujours joli et volumineux :
Dans le premier prototype de mon jeu, j'ai implémenté un système de rotation entièrement naïf, qui renversait le bloc de 90 degrés sur un point central. Il s'avère que ce n'est pas vraiment une bonne approche, car cela fait « vaciller » les blocs, se déplaçant de haut en bas lorsqu'ils tournent :
Au lieu de cela, les rotations sont codées en dur pour être « agréables » au lieu de « précises ». Une pièce est définie dans une grille de 4x4 cellules, et chaque cellule peut être remplie ou non. Il y a 4 rotations. Par conséquent : les rotations peuvent simplement être des tableaux de quatre nombres de 16 bits. Ce qui ressemble à ceci :
/**
* Example: T block
*
* As a grid:
*
* .X.. -> 0100
* XXX. -> 1110
* .... -> 0000
* .... -> 0000
*
* binary = 0b0100111000000000
* hexadecimal = 0x4E00
*
*/
typedef int16_t ShapeBits ;
static ShapeBits shapeHexes [ 8 ][ 4 ] = {
{ 0 }, // NONE
{ 0x0F00 , 0x4444 , 0x0F00 , 0x4444 }, // I
{ 0xE200 , 0x44C0 , 0x8E00 , 0xC880 }, // J
{ 0xE800 , 0xC440 , 0x2E00 , 0x88C0 }, // L
{ 0xCC00 , 0xCC00 , 0xCC00 , 0xCC00 }, // O
{ 0x6C00 , 0x8C40 , 0x6C00 , 0x8C40 }, // S
{ 0x0E40 , 0x4C40 , 0x4E00 , 0x4640 }, // T
{ 0x4C80 , 0xC600 , 0x4C80 , 0xC600 }, // Z
};
L’extraction des valeurs des cellules n’est qu’un simple masquage de bits :
#define GRID_BIT_OFFSET 0x8000;
int blocks_getShapeBit ( ShapeBits s , int y , int x ) {
int mask = GRID_BIT_OFFSET >> (( y * 4 ) + x );
return s & mask ;
}
Les choses se mettent en place maintenant avec un élan.
C’est à ce moment-là que je me suis heurté à un problème : la randomisation. Les pièces doivent apparaître de manière aléatoire pour que le jeu vaille la peine d'être joué, mais la randomisation est difficile avec les ordinateurs. Sur ma version MacOS, j'ai pu « amorcer » le générateur de nombres aléatoires avec l'horloge système, mais la PSX n'a pas d'horloge interne.
Au lieu de cela, une solution adoptée par de nombreux jeux consiste à obliger le joueur à créer la graine. Le jeu affiche un écran de démarrage ou un écran de titre avec un texte comme « appuyez sur Démarrer pour commencer », puis le timing est pris à partir de cette pression sur le bouton pour créer la graine.
J'ai créé un "graphique" en déclarant des int32
codés en binaire où 1
bit serait un "pixel" dans une rangée de briques :
Ce que je voulais, c'était que les lignes se dissolvent progressivement dans la vue. J'avais d'abord besoin d'une fonction qui permettrait de « garder une trace » du nombre de fois où elle était appelée. C facilite cela avec le mot-clé static
- s'il est utilisé dans une fonction, la même adresse mémoire et le même contenu sont réutilisés lors de l'invocation suivante.
Ensuite, à l'intérieur de cette même fonction se trouve une boucle qui parcourt les valeurs x/y de la « grille » et décide si suffisamment de ticks se sont produits pour afficher le « pixel » :
void ui_renderTitleScreen () {
static int32_t titleTimer = 0 ;
titleTimer ++ ;
// For every 2 times (2 frames) this function is called, ticks increases by 1
int32_t ticks = titleTimer / 2 ;
// Dissolve-in the title blocks
for ( int y = 0 ; y < 5 ; y ++ ) {
for ( int x = 0 ; x < 22 ; x ++ ) {
int matrixPosition = ( y * 22 ) + x ;
if ( matrixPosition > ticks ) {
break ; // because this 'pixel' of the display is not to be displayed yet
}
int32_t titleLine = titlePattern [ y ];
int32_t bitMask = titleMask >> x ;
if ( titleLine & bitMask ) { // there is a 'pixel' at this location to show
ui_renderBlock ( /* skip boring details */ );
}
}
}
}
Nous y sommes presque maintenant.
Les jeux PSX classiques démarrent en deux étapes : d'abord l'écran Sony Computer Entertainment, puis le logo PSX. Mais si nous compilons et exécutons le projet hello-psx
ce n'est pas le cas. Le deuxième écran est juste noir. Pourquoi donc?
Eh bien, le splash SCE vient du BIOS, tout comme le son de démarrage de la PSX, mais le célèbre logo fait en réalité partie des données de licence du disque. Il est là pour agir comme un « sceau d’authenticité » : toute personne piratant un jeu copie l’adresse IP de Sony ainsi que celle de l’éditeur. Cela a donné à Sony davantage d'instruments juridiques pour lutter contre le piratage de logiciels.
Si nous voulons que notre jeu affiche le logo, nous devons fournir un fichier de licence extrait d'un ISO, mais pour des raisons de droit d'auteur, nous devons l' .gitignore
.
< license file = " ${PROJECT_SOURCE_DIR}/license_data.dat " />
D'accord. Maintenant, nous sommes prêts.
Tout a commencé par un achat impulsif, ma PlayStation Yaroze noire. Ironiquement, il ne jouerait pas réellement à mon jeu car il possédait toujours son matériel anti-piratage. Je n'avais pas envie d'installer une puce sur un morceau aussi inestimable de l'histoire de la PSX - pas avec mes compétences en soudure.
Au lieu de cela, j'ai dû retrouver une PlayStation grise modifiée, qui avait toujours un lecteur décent. Je pensais que le but de mon projet était d’écrire un vrai jeu PlayStation et cela impliquait d’utiliser une vraie PlayStation.
Il fallait aussi trouver les bons médias. Le laser PSX est assez pointilleux et les CD-R modernes ont tendance à être beaucoup moins réfléchissants que les disques pressés. Mes premières tentatives avec des CD d'histoires d'épicerie ont été une perte de temps et, en l'espace d'environ deux semaines, j'ai créé de nombreux sous-verres.
Ce fut un moment sombre. Avais-je fait tout ce chemin pour échouer à graver le CD ?
Après plusieurs semaines, j'ai mis la main sur du stock spécial JVC Taiyo Yuden. D'après ce que j'ai pu lire, ils étaient assez spécialisés et généralement utilisés dans des applications industrielles. J'ai brûlé le premier disque du plateau et je m'attendais au pire.
C'était le moment de vérité :
La séquence de démarrage de la PlayStation retentissait depuis les minuscules haut-parleurs de mon moniteur et le logo classique « PS » s'affichait sur l'écran dans une résolution éclatante de 640 x 480. Le BIOS avait clairement trouvé quelque chose sur ce disque, mais beaucoup de choses pourraient échouer après ce point. L'écran est devenu noir et j'ai tendu l'oreille pour entendre le clic-clic-clic révélateur d'une erreur de lecteur.
Au lieu de cela, un par un, de petits carrés colorés ont commencé à clignoter dans l'obscurité. Ligne par ligne, ils épelèrent un mot : NOTRIS
. Ensuite : PRESS START TO BEGIN
. Le texte m'a fait signe. Que se passerait-il ensuite ?
Un jeu de Tetris, bien sûr. Pourquoi ai-je été surpris ? Écrire votre propre jeu PlayStation en C est en fait très simple : il suffit de ne commettre aucune erreur . C'est de l'informatique pour vous, en particulier pour les éléments de bas niveau. C’est dur et pointu, et c’est beau. L'informatique moderne présente des contours plus doux, mais l'essentiel n'a pas changé.
Ceux d’entre nous qui aiment les ordinateurs doivent avoir quelque chose qui ne va pas chez nous, une irrationalité de notre rationalité, une façon de nier toute l’évidence de nos yeux et de nos oreilles selon laquelle la boîte hostile de silicium est morte et inflexible. Et façonner par des machines astucieuses l’illusion qu’elle vit.