N64: คอมไพล์ใหม่เป็นเครื่องมือในการคอมไพล์ N64 ไบนารีแบบคงที่แบบคงที่ลงในรหัส C ที่สามารถรวบรวมได้สำหรับแพลตฟอร์มใด ๆ สิ่งนี้สามารถใช้สำหรับพอร์ตหรือเครื่องมือรวมถึงการจำลองพฤติกรรมได้เร็วกว่าล่ามหรือการรวมตัวกันแบบไดนามิกอย่างมีนัยสำคัญ อย่างกว้างขวางสามารถใช้ในบริบทใด ๆ ที่คุณต้องการเรียกใช้บางส่วนของไบนารี N64 ในสภาพแวดล้อมแบบสแตนด์อโลน
นี่ไม่ใช่โครงการแรกที่ใช้การรวมกันแบบคงที่ในเกมคอนโซลเกม ตัวอย่างที่รู้จักกันดีคือ Jamulator ซึ่งกำหนดเป้าหมายไปที่ NES Binaries นอกจากนี้นี่ไม่ใช่แม้แต่โครงการแรกที่ใช้การรวมกันแบบคงที่กับโครงการที่เกี่ยวข้องกับ N64: IDO recompilation recompiles คอมไพเลอร์ SGI IRIX IDO ในระบบสมัยใหม่เพื่ออำนวยความสะดวกในการจับคู่เกม N64 โครงการนี้ทำงานได้เช่นเดียวกับโครงการ Ido Static Recomp ในบางวิธีและโครงการนั้นเป็นแรงบันดาลใจหลักของฉันในการทำสิ่งนี้
recompiler ทำงานโดยการยอมรับรายการสัญลักษณ์และข้อมูลเมตาข้างไบนารีโดยมีเป้าหมายในการแยกไบนารีอินพุตออกเป็นฟังก์ชั่นที่แต่ละคนรวมกันเป็นฟังก์ชัน C ซึ่งตั้งชื่อตามข้อมูลเมตา
คำแนะนำจะถูกประมวลผลแบบทีละหนึ่งและรหัส C ที่สอดคล้องกันจะถูกปล่อยออกมาเมื่อแต่ละคนได้รับการประมวลผล การแปลนี้เป็นตัวอักษรอย่างมากเพื่อให้ความซับซ้อนต่ำ ตัวอย่างเช่นคำสั่ง addiu $r4, $r4, 0x20
ซึ่งเพิ่ม 0x20
เป็นค่า 32 บิตในไบต์ต่ำของการลงทะเบียน $r4
และเก็บสัญญาณเพิ่ม 64 บิตผลลัพธ์ใน $r4
จะถูกคอมไพล์ใหม่ลงใน ctx->r4 = ADD32(ctx->r4, 0X20);
คำสั่ง jal
(Jump-and-Link) จะถูกนำไปคอมไพล์โดยตรงลงในการเรียกใช้ฟังก์ชันและคำแนะนำ j
หรือ b
(การกระโดดและกิ่งไม้ที่ไม่มีเงื่อนไข) ที่สามารถระบุได้ว่าเป็นการเพิ่มประสิทธิภาพการโทรแบบ tail-call ก็จะถูกรวมเข้ากับการเรียกใช้ฟังก์ชั่นเช่นกัน ช่องล่าช้าสาขาได้รับการจัดการโดยคำแนะนำซ้ำ ๆ ตามความจำเป็น มีพฤติกรรมเฉพาะอื่น ๆ สำหรับคำแนะนำบางอย่างเช่น recompiler พยายามเปลี่ยนคำสั่ง jr
เป็นคำสั่งสวิตช์กรณีหากสามารถบอกได้ว่ามันถูกใช้กับตารางกระโดด recompiler ส่วนใหญ่ได้รับการทดสอบบนไบนารีที่สร้างขึ้นด้วยคอมไพเลอร์ MIPS เก่า (เช่น MIPS GCC 2.7.2 และ IDO) รวมถึงการกำหนดเป้าหมาย MIPS ที่ทันสมัย MIPS MIPS GCC ที่ทันสมัยอาจเดินทางไปคอมไพเลอร์เนื่องจากการปรับให้เหมาะสมบางอย่างสามารถทำได้ แต่กรณีเหล่านั้นอาจหลีกเลี่ยงได้โดยการตั้งค่าสถานะการรวบรวมเฉพาะ
ทุกฟังก์ชั่นเอาต์พุตที่สร้างโดย recompiler จะถูกปล่อยออกมาในไฟล์ของตัวเอง ตัวเลือกอาจมีให้ในอนาคตสำหรับฟังก์ชั่นกลุ่มเข้าด้วยกันเป็นไฟล์เอาต์พุตซึ่งจะช่วยปรับปรุงเวลาการสร้างของเอาต์พุต recompiler โดยการลดไฟล์ I/O ในกระบวนการสร้าง
เอาต์พุต Recompiler สามารถรวบรวมได้ด้วยคอมไพเลอร์ C (ทดสอบด้วย MSVC, GCC และ Clang) คาดว่าเอาต์พุตจะใช้กับรันไทม์ที่สามารถให้ฟังก์ชั่นที่จำเป็นและการใช้งานแมโครเพื่อเรียกใช้ รันไทม์มีให้ใน N64modernruntime ซึ่งสามารถมองเห็นได้ในการดำเนินการในโครงการ Zelda 64: recompiled
การซ้อนทับที่เชื่อมโยงกันแบบคงที่และการถ่ายทอดแบบ relocatable สามารถจัดการได้โดยเครื่องมือนี้ ในทั้งสองกรณีเครื่องมือปล่อยฟังก์ชั่นการค้นหาสำหรับการกระโดดและเชื่อมโยงการลงทะเบียน (เช่นพอยน์เตอร์ฟังก์ชั่นหรือฟังก์ชั่นเสมือน) ซึ่งรันไทม์ที่ให้มาสามารถใช้งานได้โดยใช้ตารางการค้นหาใด ๆ ตัวอย่างเช่นคำสั่ง jalr $25
จะได้รับการคอมไพล์ใหม่เป็น LOOKUP_FUNC(ctx->r25)(rdram, ctx);
รันไทม์สามารถรักษารายการของส่วนโปรแกรมที่โหลดและที่ที่อยู่ใดที่พวกเขาอยู่ที่จะกำหนดฟังก์ชั่นที่จะทำงานเมื่อใดก็ตามที่การค้นหาจะถูกทริกเกอร์ในระหว่างการรันไทม์
สำหรับการซ้อนทับแบบ relocatable เครื่องมือจะแก้ไขคำแนะนำที่รองรับที่มีข้อมูลการย้ายถิ่นฐาน ( lui
, addiu
, Load และ Orderss) โดยการปล่อยแมโครพิเศษที่ช่วยให้รันไทม์ย้ายฟิลด์ค่าของคำสั่งทันที ตัวอย่างเช่นคำสั่ง lui $24, 0x80C0
ในส่วนที่เริ่มต้นที่ที่อยู่ 0x80BFA100
โดยมีการย้ายไปกับสัญลักษณ์ที่มีที่อยู่ของ 0x80BFA730
จะได้รับการคอมไพล์ใหม่เป็น ctx->r24 = S32(RELOC_HI16(1754, 0X630) << 16);
โดยที่ 1754 เป็นดัชนีของส่วนนี้ รันไทม์สามารถใช้งาน Macros reloc_hi16 และ reloc_lo16 เพื่อจัดการการแก้ไขทันทีตามที่อยู่โหลดปัจจุบันของส่วน
การสนับสนุนสำหรับการย้ายถิ่นฐานสำหรับการแมป TLB กำลังจะมาถึงในอนาคตซึ่งจะเพิ่มความสามารถในการจัดทำรายการของการย้ายถิ่นฐาน MIPS32 เพื่อให้รันไทม์สามารถย้ายพวกเขาในการโหลด การรวมสิ่งนี้เข้ากับฟังก์ชั่นที่ใช้สำหรับการซ้อนทับแบบ relocatable ควรอนุญาตให้ใช้รหัสแมป TLB ส่วนใหญ่โดยไม่ต้องลงโทษประสิทธิภาพในการเข้าถึง RAM ทุกครั้ง
ผู้รวบรวมซ้ำได้รับการกำหนดค่าโดยการจัดหาไฟล์ TOML เพื่อกำหนดค่าพฤติกรรม recompiler ซึ่งเป็นอาร์กิวเมนต์เดียวที่ให้กับผู้คอมไพเลอร์ TOML เป็นที่ที่คุณระบุเส้นทางไฟล์อินพุตและเอาต์พุตรวมถึงการเลือกฟังก์ชั่นเฉพาะการเลือกใช้ฟังก์ชั่นเฉพาะของฟังก์ชั่นเฉพาะและการแก้ไขคำแนะนำเดียวในไบนารีเป้าหมาย นอกจากนี้ยังมีฟังก์ชั่นที่วางแผนไว้เพื่อให้สามารถปล่อยตะขอในเอาต์พุต recompiler โดยเพิ่มลงใน TOML ( [[patches.func]]
และ [[patches.hook]]
ส่วนของ Toml ที่เชื่อมโยงด้านล่าง) ไม่ได้ใช้งาน เอกสารเกี่ยวกับทุกตัวเลือกที่ผู้จัดคอมไพเลอร์ไม่สามารถใช้งานได้ในปัจจุบัน แต่ตัวอย่าง TOML สามารถพบได้ในโครงการ Zelda 64: คอมไพล์ใหม่ที่นี่
ปัจจุบันวิธีเดียวที่จะให้ข้อมูลเมตาที่ต้องการคือการส่งไฟล์ ELF ไปยังเครื่องมือนี้ วิธีที่ง่ายที่สุดในการรับเอลฟ์ดังกล่าวคือการตั้งค่าการถอดชิ้นส่วนหรือการสลายตัวของไบนารีเป้าหมาย แต่จะมีการสนับสนุนสำหรับการให้ข้อมูลเมตาผ่านรูปแบบที่กำหนดเองเพื่อข้ามความจำเป็นในการทำเช่นนั้นในอนาคต
เครื่องมือนี้ยังสามารถกำหนดค่าให้เข้ากับการคอมไพล์ใหม่ในโหมด "เอาต์พุตไฟล์เดียว" ผ่านตัวเลือกในการกำหนดค่า TOML สิ่งนี้จะปล่อยฟังก์ชั่นทั้งหมดใน ELF ที่ให้ไว้ในไฟล์เอาต์พุตเดียว จุดประสงค์ของโหมดนี้คือเพื่อให้สามารถรวบรวมฟังก์ชั่นเวอร์ชันแพทช์จากไบนารีเป้าหมาย
โหมดนี้สามารถรวมกับฟังก์ชันการทำงานของ linkers เกือบทั้งหมด (LD, LLD, link.exe ของ MSVC ฯลฯ ) เพื่อแทนที่ฟังก์ชั่นจากเอาต์พุต recompiler ดั้งเดิมด้วยเวอร์ชันที่แก้ไขแล้ว linkers เหล่านั้นมองหาสัญลักษณ์ในไลบรารีคงที่หากไม่พบในไฟล์อินพุตก่อนหน้าดังนั้นการจัดแพตช์ที่รวบรวมไว้กับ linker ก่อนที่จะให้เอาต์พุต recompiler ดั้งเดิมจะส่งผลให้แพตช์จัดลำดับความสำคัญมากกว่าฟังก์ชั่นที่มีชื่อเดียวกัน จากเอาต์พุต recompiler ดั้งเดิม
สิ่งนี้จะช่วยประหยัดเวลาได้อย่างมากในขณะที่วนซ้ำบนแพตช์สำหรับไบนารีเป้าหมายเช่นเดียวกับที่คุณสามารถข้ามการทดสอบใหม่บนไบนารีเป้าหมายรวมทั้งรวบรวมเอาต์พุต recompiler ดั้งเดิม ตัวอย่างของการใช้โหมดเอาต์พุตไฟล์เดียวนี้เพื่อจุดประสงค์นั้นสามารถพบได้ในโครงการ Zelda 64: คอมไพล์ใหม่ที่นี่ด้วย makefile ที่เกี่ยวข้องที่ใช้ในการสร้าง ELF สำหรับแพตช์เหล่านั้นที่นี่
Microcode RSP สามารถคอมไพล์ใหม่ด้วยเครื่องมือนี้ ขณะนี้ยังไม่มีการสนับสนุนสำหรับการซ้อนทับ RSP ใหม่ แต่อาจเพิ่มในอนาคตหากต้องการ เอกสารเกี่ยวกับวิธีการใช้ฟังก์ชั่นนี้จะมาเร็ว ๆ นี้
โครงการนี้สามารถสร้างได้ด้วย CMake 3.20 หรือสูงกว่าและคอมไพเลอร์ C ++ ที่รองรับ C ++ 20 repo นี้ใช้ git submodules ดังนั้นอย่าลืมโคลนซ้ำ ( git clone --recurse-submodules
git submodule update --init --recursive
หรือเริ่มต้น submodules ซ้ำอีกครั้งหลังจากการโคลนนิ่ง จากนั้นอาคารก็เหมือนกับโครงการ CMake อื่น ๆ เช่นเรียกใช้ cmake
ในโฟลเดอร์สร้างเป้าหมายและชี้ไปที่รากของ repo นี้จากนั้นเรียกใช้ cmake --build .
จากโฟลเดอร์เป้าหมายนั้น