Donkey Kong Country 2 กับ Open Bus
(jsgroth.dev)- บางช่วงของถังหมุนใน Donkey Kong Country 2 มีบั๊กบนอีมูเลเตอร์ SNES รุ่นเก่า ZSNES ที่ทำให้ถังยังหมุนต่อแม้ปล่อยปุ่มทิศทางแล้ว โดยสาเหตุคือยังไม่ได้ implement พฤติกรรม open bus
- บน SNES จริง เมื่ออ่านที่อยู่ที่ไม่ได้ map ไว้ จะอ่านค่าล่าสุดบน data bus กลับมาอีกครั้ง และ DKC2 พึ่งพาคุณสมบัตินี้ในการอ่าน $2000/$2001 ของ bank $B3
- รูทีนที่เป็นปัญหาจะ XOR ทิศทางเดิมของถังกับทิศทางใหม่ แล้วทำ
and $2000; บนฮาร์ดแวร์จริง การอ่าน open bus แบบ 16 บิตนี้จะคืนค่า 0x2020 เพื่อใช้ตัดสินว่าผ่านขอบเขตของทิศทางแล้วหรือไม่ - หากการอ่าน open bus เป็น 0 เหมือนใน ZSNES ผลลัพธ์ของ AND จะเป็น 0 เสมอ ทำให้ branch สำหรับจบการหมุนไม่ถูกเรียก และถังจะหมุนต่อไปจนกว่าจะกดทิศทางตรงข้าม
- มีความเป็นไปได้สูงว่าคำสั่งดังกล่าวควรเป็น
and #$2000; ใน ROM revision หนึ่ง หากเปลี่ยน opcode ที่ $33EDAC จาก0x2Dเป็น0x29ก็จะทำงานได้ปกติโดยไม่ต้องพึ่ง open bus
อาการถังหมุนไม่หยุดใน ZSNES
- Donkey Kong Country 2 มีบั๊กเก่าในบางด่านที่ทำให้ ถังหมุน ทำงานไม่ถูกต้องบน ZSNES
- ในการทำงานปกติ ผู้เล่นจะควบคุมการหมุนได้เฉพาะระหว่างที่กดปุ่มซ้ายหรือขวาอยู่ขณะอยู่ในถัง
- บน ZSNES เมื่อกดซ้ายหรือขวาหนึ่งครั้ง ถังจะหมุนไปทางนั้นต่อเนื่อง และเมื่อกดทิศทางตรงข้ามก็จะหมุนต่อเนื่องไปอีกฝั่ง
- ในช่วงหลัง ๆ ของด่าน ถังถูกวางไว้เหนือหนามหรืออุปสรรคอันตราย ทำให้บั๊กนี้เพิ่มความยากของด่านมากกว่าที่ผู้พัฒนาตั้งใจไว้
- ดูเหมือนว่า Anomie เคยพบปัญหาเดียวกันเมื่อราว 20 ปีก่อนและแก้ใน Snes9x แล้ว โดยการแก้ในเวลานั้นไม่ใช่การ emulate open bus ทั้งหมด แต่เป็นการ hardcode ค่าของ address เฉพาะที่เกมพึ่งพา
- ใน ZSNES บั๊กนี้ไม่เคยถูกแก้จนถึงที่สุด และ release สุดท้ายของโปรเจกต์คือปี 2007
Open bus ของ SNES และการกำหนดที่อยู่ของ 65816
- บน SNES แม้อ่าน memory address ที่ผิด โปรแกรมก็มักจะไม่ crash
- เมื่ออ่าน address ที่ไม่ได้ map ไว้ จะเกิดพฤติกรรม open bus ซึ่ง CPU จะอ่านค่าล่าสุดที่เคยอยู่บน data bus กลับมา
- CPU หลักของ SNES คือ 65C816 หรือ 65816 ซึ่งรวมอยู่ในแพ็กเกจ Ricoh 5A22 S-CPU
- 65816 เป็น CPU ส่วนขยาย 16 บิตของ 6502 และใน SNES ใช้ address bus 24 บิต แต่ address ส่วนใหญ่ประกอบจาก bank 8 บิตกับ offset 16 บิต
- คำสั่งจำนวนมากใช้ address 16 บิตภายใน โดย instruction fetch เกี่ยวข้องกับ program bank register ส่วนการเข้าถึงข้อมูลเกี่ยวข้องกับ data bank register
- ในสถานะถังหมุนของ DKC2 จะมีการอ่าน $2000 และ $2001 ของ bank $B3 ซึ่ง address เหล่านี้ไม่ได้ map ไปที่ใดเลยใน bank $B3 จึงกลายเป็นการอ่าน open bus
การเข้าถึงหน่วยความจำของรูทีนที่เป็นปัญหา
- เมื่อปล่อยปุ่มซ้ายขวาขณะอยู่ในถังหมุน รูทีนที่ทำงานทุกเฟรมจะทำการอ่าน open bus
- สถานะเริ่มต้นคือ data bank register เป็น
$B3, direct page เป็น$0000, และแฟล็ก M/X ทั้งคู่ clear อยู่ ทำให้ register และการเข้าถึง memory เป็นแบบ 16 บิต - รูทีนนี้ใช้ address หลายตำแหน่งในช่วง
$0000-$2001$0EE6: ทิศทางปัจจุบันของถัง$0E0A: ปริมาณการหมุนต่อเฟรม$0032: ตำแหน่งที่ดูเหมือนเป็นตัวแปรชั่วคราว
- ใน bank $00-$3F และ $80-$BF ช่วง
$0000-$1FFFจะถูก map ไปยัง 8KB แรกของ WRAM 128KB ของคอนโซล $2000-$20FFเป็นพื้นที่ที่ไม่ได้ map ดังนั้นคำสั่งand $2000จึงกลายเป็น การอ่าน open bus
วิธีที่ 0x2020 ถูกใช้ตัดสินการจบการหมุน
- รูทีนจะนำปริมาณการหมุนไปบวกกับทิศทางปัจจุบันเพื่อสร้างทิศทางใหม่ แล้วเก็บไว้ในตัวแปรชั่วคราว
- จากนั้นจะ XOR ทิศทางเดิมกับทิศทางใหม่ แล้วนำผลลัพธ์ไปใช้กับ
and $2000ก่อนตรวจว่าผลลัพธ์เป็น 0 หรือไม่ - บนฮาร์ดแวร์ SNES จริง
and $2000ซึ่งเป็นการอ่าน open bus แบบ 16 บิตจะคืนค่า 0x2020 เสมอ- machine code ของ
and $2000คือ2D 00 20 - 65816 ใช้ data bus 8 บิต จึงทำการอ่าน 16 บิตเป็นการอ่าน 8 บิตสองครั้ง
- ในกรณีนี้ byte ล่าสุดที่อ่านคือ high byte ของ address คือ
0x20ทำให้ทั้งสองครั้งคืนค่า0x20
- machine code ของ
- ดังนั้นพฤติกรรมจริงจึงเทียบเท่ากับ
and #$2020ในเชิงฟังก์ชัน - หากผล AND เป็น 0 จะเก็บทิศทางใหม่ไว้ตามเดิมและหมุนต่อในเฟรมถัดไป
- หากไม่เป็น 0 จะตั้งค่าปริมาณการหมุนเป็น 0 แล้วบวก
0x1000ให้ทิศทางใหม่ จากนั้น mask ด้วย0xE000เพื่อจัดแนวไปยังทิศทางที่เป็นพหุคูณของ0x2000ที่ใกล้ที่สุด
ทำไมใน ZSNES จึงหมุนต่อไปเรื่อย ๆ
- ค่าทิศทางของถังดูเหมือนใช้สเกลที่
0x0000ชี้ลง และ0x4000ชี้ซ้าย - ถังที่วิเคราะห์มีปริมาณการหมุนตามเข็มนาฬิกาเป็น
0x0300และทวนเข็มนาฬิกาเป็น0xFD00โดยใช้เวลาหมุนครบ 360 องศามากกว่า 85 เฟรมเล็กน้อย - ค่านี้สั้นกว่า 1.5 วินาทีเล็กน้อยเมื่อคิดที่ 60fps
- ในการ AND กับ
0x2020การเปลี่ยนแปลงที่มีความหมายจริง ๆ คือ bit 13 หรือ 0x2000 - การเปลี่ยนแปลง
0x2000ในค่าทิศทางเทียบได้กับการเปลี่ยนหนึ่งขั้นของทิศทางหลักหรือทิศทางทแยง - บนฮาร์ดแวร์ปกติ เมื่อปล่อยปุ่มทิศทาง ถังจะหมุนต่อจนถึงทิศทางหลัก/ทแยงถัดไป แล้วหยุดโดยหันไปทางนั้นพอดี
- หากการอ่าน open bus คืนค่า 0 เสมอ ผล AND ก็จะเป็น 0 เสมอ ทำให้โค้ดจบการหมุนไม่ถูกเรียก
ความเป็นไปได้ว่าเป็นการพิมพ์ผิดและผลของ ROM patch
and $2000เป็นคำสั่ง absolute addressing และในเชิงตรรกะมีความเป็นไปได้สูงว่าควรเป็นand #$2000แบบ immediate addressing- บนฮาร์ดแวร์จริง open bus คืนค่า 0x2020 ทำให้
and $2000บังเอิญทำงานตรงตามเจตนา - พฤติกรรมที่บังเอิญนี้ขึ้นกับเงื่อนไขที่ว่า 6 บิตล่างของปริมาณการหมุนต่อเฟรมเป็น 0 เสมอ ซึ่งทำให้
0x2020เทียบเท่ากับ0x2000ในเชิงฟังก์ชัน - opcode ที่ผิดถูกเรียกใช้ที่ bank
$B3offset$EDACและใน revision ที่วิเคราะห์ จะถูก map ไปยัง$33EDACภายใน ROM ขนาด 4MB ของเกม - หากเปลี่ยน byte นี้จาก
0x2Dเป็น0x29ถังหมุนจะทำงานปกติแม้การอ่าน open bus จะคืนค่า 0 เสมอ - ตำแหน่ง ROM ที่แน่นอนอาจแตกต่างกันใน revision อื่นของเกม
- เนื่องจากเกมทำงานได้ปกติบน SNES emulator แทบทั้งหมด ยกเว้น ZSNES รุ่นเก่า กรณีนี้จึงใกล้เคียงกับการวิเคราะห์เพื่อไล่ตามพฤติกรรมที่พึ่งพาฮาร์ดแวร์ มากกว่าการ patch ที่มีผลในทางปฏิบัติ
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ตอนเขียน 6502 assembly ผมเสียเวลาในชีวิตไปมากกับความผิดพลาดแบบลืมใส่
#หน้าค่า immediate แล้วกลายเป็นไปเข้าถึงหน่วยความจำแทนแบบในกรณีนี้ก็มักจะมีหลายครั้งที่มันดันทำงานได้ในบางสถานการณ์โดยบังเอิญ ที่แย่กว่าคือกรณีที่ไปพึ่งพา RAM ที่ยังไม่ได้ initialize เพราะด้วยคุณสมบัติของ DRAM มันอาจทำงานได้ดีเสมอบนเครื่องของผมหรือบนอีมูเลเตอร์ แต่พอไปอยู่บนเครื่องคนอื่นที่ใช้ชิป DRAM คนละตัวกลับพัง ปกติก็มักจะพบเรื่องนี้ตอนเหลือเวลาอีก 15 นาทีก่อนขึ้นนำเสนอใน demoparty แล้วโปรแกรมดันไม่รันบนเครื่องของงาน
LDA #2คือ “โหลดเลข 2 เข้า A” ส่วนLDA 2คือ “โหลดค่าที่อยู่ในตำแหน่งหน่วยความจำ 2 เข้า A”พอเห็นคำว่า Open Bus ในชื่อเรื่องเขียนตัวใหญ่ ผมก็เริ่มอ่านโดยนึกว่าเป็นชื่อเฉพาะของโปรโตคอลหรือมาตรฐานบัสเก่า ๆ ที่ไม่เคยได้ยินมาก่อน
พออ่านแล้วถึงเข้าใจว่ามันหมายถึงสถานะ “เปิด” ที่บัสไม่ได้ต่อกับอะไรเลย เพราะตัวถอดรหัสเส้นแอดเดรสไม่ได้เปิดใช้งานอุปกรณ์หน่วยความจำใด ๆ ที่แอดเดรส
$2000มันค่อนข้างขำที่ปัญหาจากการลืม#ใน addressing แบบ immediate เพิ่งโผล่มาเมื่ออีมูเลเตอร์เก่าไม่ได้จัดการการอ่านหน่วยความจำเหมือนฮาร์ดแวร์จริง ถ้าแก้โดยใช้ addressing แบบ immediate แทน absolute addressing ก็จะไม่ต้องอ่านหน่วยความจำด้วย ทำให้เวลารันเร็วขึ้นด้วย ในบล็อกโค้ดนั้นน่าจะเร็วขึ้นราว 2µs แต่ก็คงมีความหมายเฉพาะบน bare metal และอีมูเลเตอร์เองก็น่าจะไม่ได้แม่นเรื่อง timing แบบสมบูรณ์อยู่แล้วเท่าที่ทราบ Donkey Kong 64 มี memory leak ที่ทำให้เกมพังหลังเล่นต่อเนื่องประมาณ 8–9 ชั่วโมง ซึ่งสำหรับยุคนั้นถือว่านานเกินจริงจนไม่น่าจะเกิดขึ้นระหว่างพัฒนา แต่ถ้าใช้อีมูเลเตอร์เซฟสเตตแล้วเล่นต่อเรื่อย ๆ แทนการใช้ระบบเซฟในเกม เวลานั้นจะสะสมเร็วมาก อย่างไรก็ตาม ประวัติยังไม่ชัดเจน บางแหล่งอ้างว่าการแถม Memory Pak มาด้วยเป็นมาตรการนาทีสุดท้ายเพื่อซ่อนบั๊ก โดยยืดเวลาที่เกมแครชจาก 8–9 ชั่วโมงไปเป็น 13–20 ชั่วโมง แต่จากงานค้นคว้าล่าสุดดูเหมือนจะเป็นเรื่องบังเอิญ และไม่น่าใช่ว่า Rare หรือ Nintendo รู้ถึงบั๊กนั้นแล้วค่อยวางขาย
[0] https://arstechnica.com/gaming/2021/06/how-snes-emulators-go...
ตอนทำฟีเจอร์ RunAhead ของ RetroArch และกำลังตรวจจุดที่เซฟสเตตไม่ตรงกัน ผมเคยเห็น SNES Puyo Puyo ใช้ PPU open bus
หลังจากโหลดสถานะแล้ว ค่าที่อ่านจาก PPU Open Bus เปลี่ยนไป ทำให้ log trace การทำงานของ CPU ไม่ตรงกัน
ผมไม่ได้พลาดเรื่องตระกูล 6502 บ่อยตลอดเวลา แต่พอพลาดก็มักเป็นการใส่ memory address แทนค่า immediate
มันเป็นความผิดพลาดที่เจอบ่อยและเกิดขึ้นได้ง่ายมาก และผมเชื่อว่า Chuck Peddle เองก็เสียใจกับไวยากรณ์ที่ต้องใส่
#แบบ#$1234สำหรับค่า immediate มากพอสมควร การตั้งให้#แสดงเป็นสีแดงสดใน IDE ช่วยได้บ้าง แม้แต่มหาเทพแอสเซมบลีของ Rare ก็ยังโดนปัญหานี้intel_syntax noprefixของ GNU assemblerในคำสั่งที่รับได้ทั้งค่า immediate และแอดเดรสหน่วยความจำ มันมี ความกำกวมทางไวยากรณ์ ที่ทำให้ค่าคงที่แบบมีชื่อซึ่งอ้างถึงล่วงหน้าถูกตีความเป็นการอ้างถึงสัญลักษณ์ที่ยังไม่ทราบ ผลคือมันถูกแอสเซมบลีออกมาเป็นคำสั่งที่มีแอดเดรสหน่วยความจำแบบตัวแทนสำหรับให้ลิงเกอร์มาเติม relocation address ของสัญลักษณ์ แทนที่จะเป็นค่า immediate ตามที่คาดไว้ ดีบักกันทรมานมาก
ผมชอบบทความแบบนี้ assembly มักเป็นอะไรที่ผมตามได้แค่ราว 60% ดังนั้นการมี คำอธิบายแบบร้อยแก้ว ประกบไว้ช่วยได้มากจริง ๆ
และการได้ฟังเรื่องบั๊กในซอฟต์แวร์คลาสสิกที่ไม่มีใครเข้าใจ หรืออาจจะไม่เคยมีใครสังเกตเห็นมาจนถึงตอนนี้ ก็สนุกดี
อุปกรณ์แบบที่จำเป็นสำหรับระบบที่ต่อเครือข่ายได้ และทุกวันนี้ก็ถูกพอที่จะใส่ในสถาปัตยกรรม embedded ที่แยกขาดจากโลกภายนอกโดยสิ้นเชิงได้แล้ว บน NES เดิมนั้น การอ่านและเขียนหลายอย่างเป็นเพียงการสลับแรงดันไฟบนเส้นลวดบางเส้น แล้วจากนั้นอะไรก็เกิดขึ้นตามที่จะเกิด ผลลัพธ์ที่ต้องการได้มาจากการสลับแรงดันไฟอย่างควบคุมอย่างแม่นยำให้ตรงกับสัญญาณที่บอกช่วง blanking ของ CRT แอนิเมชันบางส่วนใน Super Mario Bros 3 ใช้การสลับ RAM multiplexer เพื่อเลือกหนึ่งในหลาย bank ของข้อมูลสไปรต์ แล้วทำให้ฮาร์ดแวร์กราฟิกไปอ่านจากชิปอีกตัวที่ต่างออกไปเล็กน้อยตอนดึงสไปรต์มาใช้ เพราะ timing ของทีวีสำคัญ ซอฟต์แวร์สำหรับเขต NTSC และ PAL จึงต้องแยกกันออกด้วย และทั้งสองแบบมีอัตรารีเฟรชต่างกัน โดยอัตรารีเฟรชนั้นก็คือ clock ที่ขับตรรกะการเรนเดอร์อยู่ มันเป็นยุคที่ดิบมากจริง ๆ
เท่าที่ผมรู้ open bus จะพบได้ในระบบยุคแรก ๆ ที่ใช้บัส synchronous แบบเรียบง่ายเท่านั้น
ระบบส่วนใหญ่อื่น ๆ น่าจะให้ค่าคงที่เป็นศูนย์ล้วนหรือหนึ่งล้วนเมื่อเข้าถึงแอดเดรสที่ไม่มีอยู่ เพราะมี handshake ที่ทำให้ master รู้ได้ว่าไม่มีการตอบสนองบนบัส ซึ่งในศัพท์ของ PCI จะเรียกว่า “master abort”
โอเพนบัสหมายถึง สายบัสข้อมูลเป็นวงจรเปิด ตามตัวอักษร
CPU วางแอดเดรสที่ไม่ได้แมปหรือเป็นแบบเขียนอย่างเดียวลงบนแอดเดรสบัส และเนื่องจากไม่มีฮาร์ดแวร์ใดบนบัสตอบสนอง สายบัสจึงไม่ได้ถูกขับสัญญาณและลอยค้างอยู่ ในทางทฤษฎีแล้วนี่คือพฤติกรรมที่ไม่กำหนดไว้ในระดับฮาร์ดแวร์
ถ้าจะเข้าใจว่าในความเป็นจริงเกิดอะไรขึ้น ต้องดูโครงสร้างทางกายภาพของดาต้าบัสเพิ่มอีกนิด มีตัวนำยาว ๆ ที่พาสัญญาณไปรอบเมนบอร์ดและคาร์ทริดจ์ โดยแยกจากกราวด์เพลนด้วยชั้นแผ่นฉนวนบาง ๆ สิ่งนี้มีลักษณะเหมือนตัวเก็บประจุ และในทางปฏิบัติวิศวกรก็อธิบายและจำลองมันด้วย ความจุไฟฟ้าแฝง ผลกระทบนี้เป็นตัวจำกัดอัตราการส่งข้อมูลสูงสุดของบัส จึงมักพยายามลดให้ต่ำที่สุด แต่เพราะผลกระทบนี้เอง เมื่อบัสไม่ได้ถูกขับสัญญาณ มันจึงมีแนวโน้มจะค้างอยู่ที่แรงดันล่าสุดที่ถูกขับไว้ มันทำงานคล้ายเซลล์ DRAM ขนาดเล็ก จึงเกิดผลตามที่บทความพูดว่า “การอ่านโอเพนบัสจะคืนค่าที่เพิ่งผ่านบัสไปล่าสุด”
การที่เกมเผลอพึ่งพาผลของโอเพนบัสแบบ DKC2 นั้นไม่ใช่เรื่องหายาก บน NES รีจิสเตอร์พอร์ตอนุกรมสำหรับต่อคอนโทรลเลอร์จะขับเฉพาะบิตล่าง ส่วนบิตบนเป็นโอเพนบัส มีเกมบางเกมที่อ่านอินพุตคอนโทรลเลอร์ด้วยคำสั่ง
LDA $4016แล้วคาดว่าจะได้ค่า$40หรือ$41โดยเลข 4 ตรงนี้คือค่าที่ค้างอยู่เพราะโอเพนบัสยังมี กลยุทธ์สปีดรัน ที่อาศัยพฤติกรรมโอเพนบัสเป็นส่วนหนึ่งของการทำ memory corruption หรือเอ็กซ์พลอยต์รันโค้ดตามอำเภอใจด้วย ตัวอย่างเช่นเครดิตวาร์ปของ Super Mario World จะส่งโปรแกรมเคาน์เตอร์ไปยังหน่วยความจำที่ไม่ได้แมป ปล่อยให้มันวิ่งต่อไปพักใหญ่ก่อนจะไปถึง RAM ในที่สุด แล้วจึงรันเพย์โหลดที่สร้างขึ้นจากการจัดตำแหน่งศัตรูอย่างประณีต [1]
อย่างไรก็ตาม แม้โดยทั่วไปพฤติกรรมโอเพนบัสจะคาดเดาได้ ก็ยังมีข้อยกเว้น คาร์ทริดจ์ที่ไม่เป็นมาตรฐานอาจคืนค่าเริ่มต้นสำหรับหน่วยความจำที่ไม่ได้แมป หรือมีตัวต้านทาน pull-up/pull-down ซึ่งส่งผลต่อพฤติกรรมของโอเพนบัสได้ นอกจากนี้ยังมีปฏิสัมพันธ์ที่น่าสนใจกับ DMA ด้วย โดย SNES รองรับฟีเจอร์ชื่อ HDMA ซึ่งให้แอปพลิเคชันตั้งเวลาให้ส่งข้อมูลจาก CPU ไปยังฮาร์ดแวร์กราฟิกอย่างแม่นยำ เพื่ออัปโหลดข้อมูลหรือเปลี่ยนการตั้งค่ากลางเฟรม [2] การส่งข้อมูลแบบ DMA นี้จะหยุด CPU ชั่วคราวเพื่อใช้บัสในการส่ง และถ้าการส่ง DMA แทรกเข้ามาระหว่างการรันคำสั่ง หลังจากอ่านแอดเดรสเป้าหมายแล้วแต่ก่อนการอ่านโอเพนบัสจริง ก็อาจเปลี่ยนพฤติกรรมการอ่านโอเพนบัสได้
กรณีขอบเขตที่เฉพาะทางมากนี้ส่งผลอย่างมากต่อเอ็กซ์พลอยต์สปีดรันของ Super Metroid [3] เอ็กซ์พลอยต์นี้พยายามทำให้เกิด
memcpyที่ออกนอกขอบเขต เพื่อย้ายบล็อกข้อมูลขนาดใหญ่จากโอเพนบัสไปยัง RAM การอ่านโอเพนบัสแทบจะคืนค่า 0 เสมอ เพราะไบต์สุดท้ายของคำสั่งโหลดที่เกี่ยวข้องเป็น 0 แต่ในห้องบางห้องที่มีเอฟเฟ็กต์กราฟิก HDMA จำนวนมาก มีโอกาสพอสมควรที่การส่ง DMA จะไปกระทบกับการอ่านครั้งใดครั้งหนึ่ง ทำให้มีไบต์ที่ไม่ใช่ 0 โผล่มาในตำแหน่งสำคัญ ส่งผลให้เอ็กซ์พลอยต์ทำงานไม่ปกติและแครช เรื่องนี้จึงกลายเป็นข้อถกเถียงเล็ก ๆ ในคอมมูนิตี้ บางเส้นทางและกลยุทธ์จึงเสถียรเฉพาะบนอีมูเลเตอร์และเฟิร์มแวร์ที่ไม่เป็นมาตรฐาน ผู้เล่นที่ใช้ฮาร์ดแวร์จริงหรืออีมูเลเตอร์ที่แม่นยำมากมีโอกาสเจอแครชสูงกว่า แต่ในทางกลับกัน อีมูเลเตอร์ส่วนใหญ่ รวมถึงเวอร์ชันวางจำหน่ายซ้ำอย่างเป็นทางการทั้งหมดของ Nintendo ไม่ได้จำลองกรณีขอบเขตพิเศษนี้ที่การส่ง HDMA กลางคำสั่งไปเปลี่ยนค่าการอ่านโอเพนบัสนอกจากนี้ TAS เคลียร์ Super Metroid ที่เร็วที่สุดในปัจจุบัน [4] ก็อาศัยปฏิสัมพันธ์กับ HDMA นี้เช่นกัน มีการพบสถานการณ์ที่การพยายามรันโอเพนบัสจะทำให้แครช แต่ปกติไม่สามารถควบคุมให้เกิดประโยชน์ได้ อย่างไรก็ตาม ด้วยการจัดการศัตรูในห้องเพื่อส่งผลต่อจังหวะเวลา CPU และใช้ HDMA ให้วางคำสั่งที่เป็นประโยชน์ลงบนบัสในจังหวะที่เหมาะสม ก็สามารถทำให้คอนโซลเอาอินพุตคอนโทรลเลอร์ไปรันเป็นโค้ดได้สำเร็จ จนบรรลุการรันโค้ดตามอำเภอใจอย่างสมบูรณ์
[1]: https://youtu.be/vAHXK2wut_I
[2]: https://youtu.be/K7gWmdgXPgk
[3]: https://youtu.be/CnThmKhtfOs
[4]: https://tasvideos.org/8214S
ซีรีส์วิดีโอของเขาเกี่ยวกับการสร้างคอมพิวเตอร์บนเบรดบอร์ดด้วย 6502 ช่วยให้เข้าใจทั้งเนื้อหาในบทความและคำอธิบายปัญหาฮาร์ดแวร์ได้จริง แน่นอนว่าต้องเอาตัวอย่างบัสพื้นฐานของเขาไปขยายความต่อเป็นเครื่องเชิงพาณิชย์ด้วย ถ้าไม่ได้ดู ก็คงแทบไม่เข้าใจเลย
https://eater.net
ตอนเขียนโปรแกรมชิป Parallax Propeller ก็มีปัญหาคล้าย ๆ กันอยู่บ้าง
ต้องใช้
JMP #addressเพื่อกระโดดไปยังตำแหน่งหน่วยความจำที่ระบุ แต่กลับเผลอใช้JMP addressอยู่เรื่อย ๆ ซึ่งเป็นการกระโดดไปยังแอดเดรสที่อ่านได้จากตำแหน่งหน่วยความจำที่ระบุ น่าจะเพราะยังมีแอสเซมบลี 6502 ติดเป็น muscle memory อยู่Propeller:
JMP #address6502:
JMP addressPropeller:
JMP address6502:
JMP (address)ที่แย่ที่สุดคือเหมือนในบทความนี้ โค้ด Propeller ที่มีบั๊กแบบนี้บางครั้งก็ดันทำงานได้ แล้วจู่ ๆ ก็หยุด จนต้องเสียเวลาหลายชั่วโมงเพื่อหาว่าทำไม
กราฟิก 3D แบบพรีเรนเดอร์ด้วย SGI ของ DKC 1 ล้ำสมัยมากในยุคนั้น Vector Man บน Genesis ก็ทำอะไรคล้ายกัน แต่ไม่ค่อยได้รับความสนใจเท่าไร
แทบไม่อยากเชื่อสายตาตัวเอง จำได้ว่าช่วงใกล้วางขายมีม้วนวิดีโอที่ทั้งโปรโมตเกมและเล่าเบื้องหลังการพัฒนา น่าจะเป็นของประชาสัมพันธ์ที่ขอรับมาจากอะไรสักอย่างอย่างกล่องซีเรียล ดูเทปนั้นซ้ำเยอะมาก แม้จะไม่ได้เป็นเจ้าของ DKC เอง แต่ก็ได้เล่นที่บ้านเพื่อน
นิตยสารในยุคนั้นมักพูดเรื่องนี้แบบคลุมเครือ และชวนให้เข้าใจว่า SNES มีพลังพอจะเรนเดอร์ตัวละครอะไรทำนองนั้นแบบเรียลไทม์ ทั้งที่จริงแล้วมันใกล้เคียงกับแอนิเมชันแบบฟลิปบุ๊กโดยเนื้อแท้
เวลาเล่นเกมผ่านอีมูเลชันแล้วติดขัด ก็มักจะสงสัยในที่สุดว่าเป็น บั๊กของอีมูเลเตอร์ หรือเปล่า
ถ้าเป็นปัญหาเฉพาะจุดนี้ ก็คงจะคิดว่าเกมถูกออกแบบมาแบบนี้ตั้งแต่แรกและแค่มันยากเฉยๆ ไม่ได้เกี่ยวกันโดยตรงนัก แต่เวลาเกมรู้สึกยากมากจริงๆ ก็จะคิดคล้ายๆ กันว่า “นี่เป็นเพราะความหน่วงของอีมูเลชันหรือเปล่า?” สุดท้ายพอขุดเรื่องนี้ลึกเข้าไป ก็เลยประกอบ MiSTer FPGA ขึ้นมาเอง
จำได้ว่ามีช่วงหนึ่งที่หลังจากจับหนูแล้ว ต้องกดปุ่มสี่ปุ่มพร้อมกัน แต่การรับอินพุตผ่าน USB ส่งได้พร้อมกันแค่ 3 ปุ่ม เลยต้องกดมั่วๆ ทั้งสี่ปุ่มเพื่อให้ในช่วงเวลาสั้นมากๆ มันถูกลงทะเบียนครบทั้งหมด ต้องลองหลายครั้งและหงุดหงิดมาก
อย่างที่บอกไป ผมแค่คิดว่าการกะจังหวะยิงออกจากถังเพื่อให้ได้มุมที่ถูกต้องเป็นการออกแบบเกมตามปกติ เลยตกใจมากที่รู้ว่ามันเป็นบั๊ก
พอลองรันด้วยอีมูเลเตอร์ช่วงต้นยุค 2000 มันยากกว่าที่จำได้มาก พอมาทีหลังถึงรู้ว่ามีบั๊กอีมูเลชันที่ทำให้แม้จะระเบิดฐานแล้ว ศัตรูก็ยังไม่หายไป และ Ladd ก็ยังคงถูกแช่แข็งอยู่ ดังนั้นถ้าจะเคลียร์ด่านให้ได้ ก็ต้องมีพลังชีวิตเพิ่มอีกราว 2 ขีด ครั้งหนึ่งเคยผ่านได้แบบนั้นเพื่อดูว่าทำได้ไหม แต่ก็ไม่เคยทำอีกเลย