@@ -38,6 +38,9 @@ pub const Libs = struct {
3838 crt0_obj2 : ? * std.Build.Step.Compile = null ,
3939 // NES only: FamiTone2 music/sound engine + banked fixed-bank wrappers.
4040 famitone2 : ? * std.Build.Step.Compile = null ,
41+ // MEGA65/Commodore: save-basic.S saves/restores ZP and overrides _Exit to return
42+ // cleanly to BASIC. Must be a TRUE object (section-only .init.005/.fini.989).
43+ save_basic : ? * std.Build.Step.Compile = null ,
4144};
4245
4346/// Build platform libraries for `pd` from the SDK source tree at `sdk_root`.
@@ -249,7 +252,25 @@ pub fn buildPlatform(b: *std.Build, sdk_root: []const u8, pd: Platform, opt: std
249252 .files = &.{"crt0.S" },
250253 });
251254 crt0_obj .lto = .none ;
252- return .{ .crt = libcrt , .crt0 = libcrt0 , .c = libc , .printf = libprintf , .mem = mem_obj , .crt0_obj = crt0_obj };
255+ // save-basic.S: TRUE object for MEGA65/Commodore clean BASIC exit (PR #430).
256+ // Contains .init.005 (save ZP) and .fini.989 (restore ZP) section-only contributions
257+ // with no exported symbol → must be a TRUE object, not an archive member (Hard rule #5).
258+ var save_basic_obj : ? * std.Build.Step.Compile = null ;
259+ if (std .mem .eql (u8 , pd .name , "mega65" )) {
260+ const sb_obj = b .addObject (.{
261+ .name = "save-basic" ,
262+ .root_module = b .createModule (.{ .target = target , .optimize = opt }),
263+ });
264+ sb_obj .root_module .addIncludePath (.{ .cwd_relative = com_asm });
265+ sb_obj .root_module .addIncludePath (.{ .cwd_relative = com_inc });
266+ sb_obj .root_module .addCSourceFiles (.{
267+ .root = .{ .cwd_relative = comm_dir },
268+ .files = &.{"save-basic.S" },
269+ });
270+ sb_obj .lto = .none ;
271+ save_basic_obj = sb_obj ;
272+ }
273+ return .{ .crt = libcrt , .crt0 = libcrt0 , .c = libc , .printf = libprintf , .mem = mem_obj , .crt0_obj = crt0_obj , .save_basic = save_basic_obj };
253274 }
254275
255276 return .{ .crt = libcrt , .crt0 = libcrt0 , .c = libc };
0 commit comments