Nortis (sebelumnya Notris) adalah game PSX buatan sendiri, ditulis dalam C menggunakan peralatan modern. Ini sepenuhnya dapat dimainkan pada perangkat keras asli dan didukung oleh PSNoobSDK.
Lihat basis kode PSX di sini.
Tahun lalu saya mendapatkan PlayStation 1 berwarna hitam yang langka. Ini disebut Net Yaroze dan merupakan konsol khusus yang dapat memainkan game rumahan serta judul PSX biasa. Itu adalah bagian dari proyek khusus Sony untuk mengajak para penghobi dan pelajar memasuki industri game.
Game Yaroze sangat terbatas, karena Sony tidak ingin pembuat kode kamar tidur bersaing dengan pengembang komersial. Mereka hanya dapat dimainkan di Yarozes lain atau di disk demo khusus. Mereka harus masuk seluruhnya ke dalam RAM sistem tanpa akses ke CD-ROM. Terlepas dari keterbatasan ini, Yaroze membina komunitas pengembang indie yang penuh semangat.
Dan sekarang aku punya milikku sendiri. Yang membuat saya berpikir: seperti apa sebenarnya menulis game PlayStation?
Ini tentang bagaimana saya menulis sendiri game PSX homebrew sederhana, menggunakan perpustakaan versi sumber terbuka tetapi masih berjalan pada perangkat keras asli dan ditulis dalam C klasik.
Lewati bagian ini
Game PSX biasanya ditulis dalam C pada workstation Windows 9X. Devkit resminya adalah sepasang kartu ekspansi ISA yang dimasukkan ke motherboard PC IBM umum dan berisi seluruh chipset sistem PSX, video out, dan RAM tambahan (8MB, bukan 2MB). Ini memberikan keluaran TTY dan debugger ke mesin host.
Anda mungkin pernah mendengar tentang PlayStation biru. Ini adalah untuk QA dan bukan pengembangan dan identik dengan unit ritel kecuali bahwa mereka dapat memutar CD-ROM yang dibakar. Namun, setidaknya satu perusahaan menjual addon khusus untuk mengubahnya menjadi devkit:
Desainnya sangat ramah pengembang. Anda dapat memainkan game Anda di CRT dengan pengontrol normal sambil menelusuri breakpoint GDB di PC Windows 95 Anda, membuka-buka buku teks tebal tentang fungsi C SDK.
Pada prinsipnya, pengembang PSX dapat bekerja sepenuhnya di C. SDK terdiri dari sekumpulan pustaka C yang disebut PSY-Q, dan menyertakan program kompiler ccpsx
yang sebenarnya hanyalah frontend dari GCC. Hal ini mendukung serangkaian pengoptimalan, seperti penyebarisan kode dan pembukaan loop, meskipun bagian penting performa masih memerlukan perakitan yang dioptimalkan secara manual.
(Anda dapat membaca tentang pengoptimalan tersebut di slide konferensi SCEE ini).
C++ didukung oleh ccpsx
tetapi memiliki reputasi menghasilkan kode yang 'membengkak', serta waktu kompilasi yang lebih lambat. Sebenarnya, C adalah lingua franca pengembangan PSX, namun beberapa proyek menggunakan bahasa skrip dinamis di atas mesin dasar. Misalnya, Metal Gear Solid menggunakan TCL untuk skrip level; dan game Final Fantasy melangkah lebih jauh dan menerapkan bahasa bytecode mereka sendiri untuk pertempuran, sistem lapangan, dan minigame. (Anda dapat mempelajari lebih lanjut tentang ini di sini).
( Untuk bacaan lebih lanjut lihat https://www.retroreversing.com/official-playStation-devkit )
Lewati bagian ini
Namun saya sampai pada hal ini dari sudut pandang yang sangat berbeda: seorang insinyur perangkat lunak pada tahun 2024 yang sebagian besar bekerja pada aplikasi web. Pengalaman profesional saya hampir secara eksklusif menggunakan bahasa tingkat tinggi seperti JavaScript dan Haskell; Saya telah melakukan sedikit pekerjaan OpenGL dan C++, tetapi C++ modern hampir merupakan bahasa yang sepenuhnya berbeda dengan C.
Saya tahu SDK PSX ada untuk bahasa seperti Rust, tapi saya ingin merasakan cita rasa pemrograman PSX 'nyata', seperti yang dilakukan di tahun 90an. Jadi itu akan menjadi rantai alat modern dan perpustakaan sumber terbuka, tetapi semuanya menggunakan C.
Game tersebut harus berbentuk 2D yang dapat dibuat prototipenya dalam beberapa hari. Saya memilih tiruan Tetris - saya pikir itu akan cukup rumit untuk merasakan apa yang saya inginkan.
Langkah pertama adalah membangun prototipe dengan teknologi yang sudah dikenal. Ini akan memungkinkan saya untuk memahami desain dasarnya, kemudian logikanya dapat diterjemahkan sedikit demi sedikit ke dalam C.
Sebagai pengembang web, teknologi pembuatan prototipe yang paling jelas adalah JavaScript: sederhana, ringkas, mudah di-debug, dan menggunakan API grafis <canvas>
HTML5. Segalanya terjadi dengan sangat cepat
Pada saat yang sama, saya khawatir bahwa fitur JavaScript tingkat tinggi akan sulit untuk di-porting. Apa pun yang menggunakan kelas atau penutupan perlu ditulis ulang sepenuhnya, jadi saya berhati-hati membatasi diri pada subset bahasa prosedural yang sederhana.
Sekarang, saya sebenarnya mempunyai motif tersembunyi dalam mengambil proyek ini: itu adalah alasan untuk akhirnya belajar C. Bahasa tersebut membayangi pikiran saya dan saya mulai mengembangkan rasa rendah diri karena tidak mengetahuinya.
C memiliki reputasi yang mengintimidasi dan saya takut akan cerita horor tentang petunjuk yang menggantung, pembacaan yang tidak selaras, dan segmentation fault
yang ditakuti. Lebih tepatnya: Saya khawatir jika saya mencoba belajar C, dan gagal, saya akan menyadari bahwa saya sebenarnya bukanlah programmer yang baik.
Untuk mempermudah, saya pikir saya bisa menggunakan SDL2 untuk menangani input dan grafik, dan mengkompilasi untuk lingkungan desktop saya (MacOS). Itu akan memberi saya siklus build/debug yang cepat dan membuat kurva pembelajaran selembut mungkin.
Terlepas dari ketakutan saya, menurut saya C sangat menyenangkan. Sangat cepat itu 'diklik' bagi saya. Anda memulai dari primitif yang sangat sederhana - struct, karakter, fungsi - dan membangunnya menjadi lapisan abstraksi hingga akhirnya Anda berada di atas keseluruhan sistem kerja.
Game ini hanya membutuhkan waktu beberapa hari untuk porting, dan saya sangat puas dengan proyek C pertama saya yang sebenarnya. Dan saya belum pernah mengalami satu pun segfault!
SDL menyenangkan untuk digunakan, namun ada beberapa aspek yang mengharuskan saya mengalokasikan memori secara dinamis. Ini akan menjadi larangan di PlayStation, karena malloc
yang disediakan oleh kernel PSX tidak berfungsi dengan baik. Dan jalur grafis akan menjadi lompatan yang lebih besar...
Terkait PlayStation homebrew, ada dua pilihan utama untuk SDK Anda. Salah satu:
Ada beberapa opsi lain seperti C++ Psy-Qo , dan Anda bahkan dapat melupakan SDK apa pun hanya untuk melakukan sendiri I/O yang dipetakan memori - tetapi saya tidak cukup berani untuk itu.
Masalah terbesar dengan Psy-Q adalah kode tersebut masih merupakan kode milik Sony, bahkan 30 tahun kemudian. Secara hukum, minuman rumahan apa pun yang dibuat dengan bahan tersebut berisiko. Itulah yang menenggelamkan demake Portal64: ia terhubung secara statis libultra
, yang merupakan N64 SDK milik Nintendo.
Tapi sejujurnya, alasan utama saya memilih PSNoobSDK adalah karena dokumentasinya sangat baik dan pengaturannya mudah. API ini sangat mirip dengan Psy-Q: sebenarnya untuk banyak fungsi saya hanya dapat melihat referensi tercetak yang disertakan dengan Yaroze saya.
Jika saya menggunakan SDK yang tidak autentik menyinggung perasaan murni PSX dalam diri Anda, silakan berhenti membaca sekarang dengan rasa jijik.
Tugas pertama saya adalah semacam hello world: dua kotak dengan latar belakang berwarna. Kedengarannya sederhana, bukan?
Lewati bagian ini
(*Beberapa di antaranya disederhanakan. Untuk panduan yang lebih resmi, baca tutorial PSNoobSDK)
Pertama-tama, bayangkan PSX VRAM sebagai kanvas besar berukuran 1024 x 512 dengan piksel 16-bit. Secara keseluruhan itu membuat 1 megabyte memori dibagikan oleh framebuffer dan tekstur. Kita dapat memilih resolusi framebuffer keluaran - bahkan hingga 640x480 piksel jika kita serakah - tetapi lebih banyak resolusi = lebih sedikit tekstur.
Sebagian besar game PSOne (dan... game pada umumnya) memiliki gagasan rendering buffer ganda: saat satu frame sedang disiapkan, frame lainnya dikirim ke layar. Jadi kita perlu mengalokasikan dua frame buffer:
(Sekarang Anda dapat melihat mengapa 640x480 tidak praktis - tidak ada cukup ruang untuk dua buffer 480p. Namun mode ini BISA digunakan oleh hal-hal seperti logo startup PSX, yang tidak memerlukan banyak animasi)
Buffer (disebut secara bergantian sebagai lingkungan tampilan dan gambar) ditukar setiap frame. Sebagian besar game PSX menargetkan 30fps (di Amerika Utara) tetapi interupsi VSync sebenarnya terjadi pada 60hz. Beberapa game berhasil berjalan pada 60 fps penuh - Tekken 3 dan Kula World (Roll Away) terlintas dalam pikiran - tetapi jelas Anda perlu merendernya dalam separuh waktu. Ingat kita hanya memiliki kekuatan pemrosesan 33 Mhz.
Tapi - bagaimana cara kerja proses menggambarnya? Hal ini dilakukan oleh GPU, namun GPU PSX bekerja sangat berbeda dengan kartu grafis modern. Pada dasarnya, setiap frame GPU dikirimkan daftar 'paket' atau perintah grafis yang diurutkan. "Gambarlah segitiga di sini", "muat tekstur ini untuk menguliti segi empat berikutnya", dan seterusnya.
GPU tidak melakukan transformasi 3D; itulah tugas koprosesor GTE (Geometry Transform Engine). Perintah GPU mewakili grafik 2D murni, yang sudah dimanipulasi oleh perangkat keras 3D.
Artinya, jalur piksel PSX adalah sebagai berikut:
Jadi dalam pseudocode frame loop PSX (pada dasarnya) berjalan seperti ini
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
}
Anda dapat melihat dari sini bahwa meskipun frame 1 ada di layar, frame 2 masih dicat, dan frame 3 berpotensi masih 'dibangun' oleh program itu sendiri. Kemudian setelah DrawSync/VSync kita kirim frame 2 ke TV, dan dapatkan gambar GPU frame 3.
Seperti disebutkan, GPU adalah perangkat keras yang sepenuhnya 2D, tidak mengetahui koordinat z dalam ruang 3D. Tidak ada "z-buffer" untuk mendeskripsikan oklusi - yaitu objek mana yang berada di depan objek lain. Jadi bagaimana barang diurutkan di depan orang lain?
Cara kerjanya adalah tabel pemesanan terdiri dari rantai perintah grafis yang terhubung terbalik. Ini dilintasi dari belakang ke depan untuk mengimplementasikan algoritma pelukis .
Tepatnya, tabel pemesanan adalah daftar tertaut terbalik. Setiap item memiliki penunjuk ke item sebelumnya dalam daftar, dan kami menambahkan primitif dengan memasukkannya ke dalam rantai. Umumnya PL diinisialisasi sebagai array tetap, dengan masing-masing elemen dalam array mewakili 'level' atau lapisan dalam tampilan. PL dapat disarangkan untuk mengimplementasikan adegan yang kompleks.
Diagram berikut membantu menjelaskannya (sumber)
Pendekatan ini tidak sempurna dan terkadang geometri PSX menunjukkan kliping yang aneh, karena setiap poli hanya dapat berada pada satu 'indeks z' di ruang layar, namun berfungsi cukup baik untuk sebagian besar game. Saat ini keterbatasan seperti itu dianggap sebagai bagian dari daya tarik khas PSX.
Lewati bagian ini
Kita telah membicarakan banyak teori - seperti apa praktiknya?
Bagian ini tidak akan membahas semua kode baris demi baris tetapi akan memberi Anda gambaran tentang konsep grafis PSX. Jika Anda ingin melihat kode lengkap, buka hello-psx/main.c
.
Atau jika Anda bukan seorang pembuat kode, silakan lewati saja. Ini hanya untuk teknisi yang penasaran.
Hal pertama yang kita perlukan adalah beberapa struct untuk menampung buffer kita. Kita akan memiliki RenderContext
yang berisi dua RenderBuffers
, dan masing-masing RenderBuffer
akan berisi:
displayEnv
(menentukan area VRAM dari buffer tampilan saat ini)drawEnv
(menentukan area VRAM dari buffer draw saat ini)orderingTable
(daftar tertaut terbalik yang akan berisi pointer ke paket grafis)primitivesBuffer
(struct untuk paket/perintah grafis - termasuk semua poligon) #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 };
Setiap frame kita akan membalikkan bufferID
yang berarti kita dapat bekerja dengan lancar pada satu frame sementara frame lainnya sedang ditampilkan. Detail utamanya adalah p_primitive
terus-menerus diarahkan ke byte berikutnya dalam primitivesBuffer
saat ini. Sangat penting bahwa ini bertambah setiap kali primitif dialokasikan dan direset pada akhir setiap frame.
Sebelum melakukan apa pun, kita perlu menyiapkan lingkungan tampilan dan gambar, dalam konfigurasi terbalik sehingga DISP_ENV_1
menggunakan VRAM yang sama dengan DRAW_ENV_0
, dan sebaliknya
// 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 );
Saya cukup padat di sini - tetapi dari sini setiap frame pada dasarnya berjalan seperti itu
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 );
}
Ini mungkin banyak hal yang perlu diperhatikan. Jangan khawatir.
Jika Anda benar-benar ingin memahami hal ini, hal terbaik adalah melihat hello-psx/main.c
. Semuanya dikomentari dengan cukup detail. Alternatifnya, ikuti tutorial PSNoobSDK... cukup singkat dan ditulis dengan cukup jelas.
Sekarang... bagaimana kita menggambar sesuatu? Kami menulis struct ke dalam buffer primitif kami. Buffer ini diketik hanya sebagai daftar chars
yang besar sehingga kita memasukkannya ke dalam bentuk/struktur perintah kita, lalu memajukan penunjuk buffer primitif menggunakan 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 );
Kami baru saja menyisipkan kotak kuning! ? Cobalah untuk menahan kegembiraan Anda.
Lewati bagian ini
Pada titik perjalanan saya ini, yang saya miliki hanyalah program demo "hello world", dengan grafis dasar dan input pengontrol. Anda dapat melihat dari kode di hello-psx
yang saya dokumentasikan semaksimal mungkin, benar-benar untuk keuntungan saya sendiri. Program kerja merupakan sebuah langkah positif namun bukan sebuah permainan nyata.
Sudah waktunya untuk menjadi nyata .
Permainan kami perlu menunjukkan skor.
PSX tidak memberi Anda banyak hal dalam rendering teks. Ada font debug (ditampilkan di atas) tetapi ini sangat mendasar - untuk pengembangan dan tidak banyak lagi.
Sebagai gantinya, kita perlu membuat tekstur font, dan menggunakannya untuk menguliti paha depan. Saya membuat font monospace dengan https://www.piskelapp.com/ dan mengekspornya sebagai PNG transparan:
Tekstur PSX disimpan dalam format yang disebut TIM. Setiap file TIM terdiri dari:
Karena lokasi tekstur VRAM 'dimasukkan ke dalam' file TIM, Anda memerlukan alat untuk mengelola lokasi tekstur Anda. Saya merekomendasikan https://github.com/Lameguy64/TIMEedit untuk ini.
Dari sana kita hanya memiliki fungsi untuk menguliti sekumpulan paha depan, dengan offset UV berdasarkan pada setiap nilai ASCII.
Kita membutuhkan ruang agar potongan-potongan itu bisa masuk. Akan mudah untuk menggunakan persegi panjang putih yang membosankan untuk ini, tapi saya menginginkan sesuatu yang lebih terasa... PlayStation
Antarmuka pengguna kami menyatu. Bagaimana dengan potongannya?
Kini hadir beberapa desain visual penting. Idealnya setiap bata harus terlihat jelas dengan tepi yang tajam dan teduh. Kami melakukan ini dengan dua segitiga dan segi empat:
Pada resolusi asli 1x, efeknya akan kurang jelas, namun tetap terlihat bagus dan tebal:
Dalam prototipe pertama permainan saya, saya menerapkan sistem rotasi naif penuh, yang akan membalik balok 90 derajat pada titik pusat. Ternyata ini bukan pendekatan yang bagus, karena menyebabkan balok 'goyah', bergeser ke atas dan ke bawah saat berputar:
Sebaliknya, rotasi dikodekan menjadi 'bagus' dan bukan 'akurat'. Sepotong ditentukan dalam kotak sel 4x4, dan setiap sel dapat diisi atau tidak diisi. Ada 4 putaran. Oleh karena itu: rotasi hanya dapat berupa array dari empat angka 16-bit. Yang terlihat seperti ini:
/**
* 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
};
Mengekstraksi nilai sel hanyalah kasus penyembunyian bit sederhana:
#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 ;
}
Segalanya berjalan seiring dengan momentum.
Pada titik inilah saya menemui hambatan: pengacakan. Potongan harus muncul secara acak agar permainan layak dimainkan, tetapi pengacakan sulit dilakukan dengan komputer. Pada versi MacOS saya, saya dapat 'menyemai' generator angka acak dengan jam sistem, tetapi PSX tidak memiliki jam internal.
Sebaliknya, solusi yang diambil oleh banyak permainan adalah membuat pemain menciptakan benih. Permainan menampilkan splash atau layar judul dengan teks seperti 'tekan mulai untuk memulai', dan kemudian waktu diambil dari penekanan tombol tersebut untuk membuat benih.
Saya membuat 'grafik' dengan mendeklarasikan beberapa int32
yang dikodekan biner di mana setiap 1
bit akan menjadi 'piksel' dalam deretan batu bata:
Yang saya inginkan adalah garis-garis itu perlahan-lahan menghilang. Pertama, saya memerlukan fungsi yang secara efektif 'melacak' berapa kali fungsi tersebut dipanggil. C membuatnya mudah dengan kata kunci static
- jika digunakan di dalam suatu fungsi, alamat memori dan konten yang sama akan digunakan kembali pada pemanggilan berikutnya.
Kemudian di dalam fungsi yang sama ini terdapat loop yang melewati nilai x/y dari 'kisi', dan memutuskan apakah cukup centang yang terjadi untuk menampilkan 'piksel':
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 */ );
}
}
}
}
Kita hampir sampai sekarang.
Game PSX klasik melakukan booting dalam dua tahap: pertama layar Sony Computer Entertainment, lalu logo PSX. Namun jika kita mengkompilasi dan menjalankan proyek hello-psx
tidak akan terjadi. Layar kedua hanya berwarna hitam. Mengapa demikian?
Ya, SCE splash berasal dari BIOS, seperti halnya suara boot PSX, tetapi logo terkenal sebenarnya adalah bagian dari data lisensi disk. Itu ada untuk bertindak seperti 'segel keaslian' - jadi siapa pun yang membajak sebuah game berarti menyalin IP Sony dan juga penerbitnya. Hal ini memberi Sony lebih banyak instrumen hukum untuk menindak pembajakan perangkat lunak.
Jika kita ingin game kita menampilkan logonya, kita perlu menyediakan file lisensi yang diekstraksi dari ISO, tetapi demi hak cipta kita harus .gitignore
.
< license file = " ${PROJECT_SOURCE_DIR}/license_data.dat " />
Oke. Sekarang kami siap.
Ini semua dimulai dengan pembelian impulsif, Yaroze PlayStation hitam saya. Ironisnya, ia tidak benar-benar memainkan permainan saya karena ia masih memiliki perangkat keras anti-pembajakan. Saya tidak suka memasang modchip pada bagian sejarah PSX yang tak ternilai harganya - tidak dengan keterampilan menyolder saya.
Sebaliknya, saya harus mencari PlayStation abu-abu yang telah dimodifikasi, yang masih memiliki drive yang layak. Saya pikir tujuan proyek saya adalah menulis game PlayStation yang sebenarnya dan itu berarti menggunakan PlayStation yang sebenarnya .
Saya juga harus menemukan media yang tepat. Laser PSX cukup pilih-pilih dan CD-R modern cenderung kurang reflektif dibandingkan disk yang ditekan. Upaya pertama saya dengan CD cerita belanjaan hanya membuang-buang waktu, dan dalam kurun waktu sekitar dua minggu saya membuat banyak tatakan gelas.
Ini adalah momen yang kelam. Apakah saya sudah sejauh ini, hanya gagal dalam membakar CD ?
Setelah beberapa minggu saya mendapatkan beberapa saham spesial JVC Taiyo Yuden. Dari apa yang saya baca, ini cukup khusus, dan biasanya digunakan dalam aplikasi industri. Saya membakar disk pertama di piring dan saya memperkirakan yang terburuk.
Inilah momen kebenarannya:
Urutan boot PlayStation terdengar dari speaker monitor kecil saya dan logo klasik "PS" muncul di layar dalam resolusi 640 x 480 yang cerah. BIOS jelas telah menemukan sesuatu pada disk itu, tetapi banyak hal yang bisa gagal setelah titik ini. Layar menjadi hitam dan saya menajamkan telinga untuk mencari tanda klik-klik-klik dari kesalahan drive.
Sebaliknya, satu demi satu, kotak-kotak kecil berwarna mulai berkedip dari kegelapan. Baris demi baris mereka mengeja sebuah kata: NOTRIS
. Kemudian: PRESS START TO BEGIN
. Teks itu memberi isyarat padaku. Apa yang akan terjadi selanjutnya?
Tentu saja permainan Tetris. Mengapa saya terkejut? Menulis game PlayStation Anda sendiri di C sebenarnya sangat sederhana: yang diperlukan hanyalah tidak membuat kesalahan apa pun . Itu komputasi untuk Anda, terutama hal-hal tingkat rendah. Itu keras, tajam, dan indah. Komputasi modern memiliki keunggulan yang lebih lembut tetapi hal-hal penting tidak berubah.
Kita yang menyukai komputer pasti mempunyai sesuatu yang salah dengan diri kita, sebuah irasionalitas terhadap rasionalitas kita, sebuah cara untuk menyangkal semua bukti dari mata dan telinga kita bahwa kotak silikon yang bermusuhan itu sudah mati dan keras kepala. Dan fesyen dengan cerdik menciptakan ilusi bahwa ia hidup.