@playcraft/devkit 1.0.31-beta.15 → 1.0.31-beta.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,4 +11,4 @@
11
11
  * Creator web-mobile gameplay bundle. Engine (`cocos-js`, `assets/internal`)
12
12
  * and the resources asset table stay as Creator emitted them.
13
13
  * @param {string} objKey path inside the web-mobile folder
14
- */function isCocosBusinessScript(objKey){const key=String(objKey||"").replace(/\\/g,"/").replace(/^\//,"");return /^assets\/main\/index(?:\.[^/]+)?\.js$/.test(key)}async function applyObfuscation(code,level){const n=Number(level)||0;if(n<=0||typeof code!=="string"||!code)return code;let out=code;const obf=obfuscatorOptionsForCocos(n);if(obf){if(n>=4){console.log("[cocos] level 4 on the main bundle uses base64 string arrays; RC4 and full control-flow do not finish")}const JavaScriptObfuscator=require("javascript-obfuscator");out=JavaScriptObfuscator.obfuscate(out,obf).getObfuscatedCode()}if(n>=1){const{minify}=require("terser");const result=await minify(out,terserOptions());if(!result||!result.code){throw new Error(`terser produced empty output at obfuscate level ${n}`)}out=result.code}return out}module.exports={LEVEL_NAMES,obfuscateLevelName,obfuscatorOptionsForLevel,obfuscatorOptionsForCocos,terserOptions,isCocosBusinessScript,applyObfuscation};
14
+ */function isCocosBusinessScript(objKey){const key=String(objKey||"").replace(/\\/g,"/").replace(/^\//,"");return /^assets\/main\/index(?:\.[^/]+)?\.js$/.test(key)}/** Slot for asset payloads. Game logic still goes through the obfuscator. */const RAW_ASSET_SLOT="__PLAYCRAFT_RAW__";function decodeJsString(body){return body.replace(/\\(.)/g,(_,ch)=>{if(ch==="n")return"\n";if(ch==="r")return"\r";if(ch==="t")return"\t";return ch})}/** data URL or a long bare base64 blob. Short gameplay strings stay in the obfuscator. */function isRawAssetLiteral(value){if(typeof value!=="string"||value.length<64)return false;if(/^data:(?:image|audio|video|font|application)\//.test(value))return true;return value.length>=512&&/^[A-Za-z0-9+/]+=*$/.test(value)}function peelAssetLiterals(code){const blobs=[];const peeled=String(code).replace(/(['"])((?:\\.|(?!\1)[^\\])*)\1/g,(match,_quote,body)=>{const value=decodeJsString(body);if(!isRawAssetLiteral(value))return match;const index=blobs.length;blobs.push(value);return`globalThis.${RAW_ASSET_SLOT}[${index}]`});return{peeled,blobs}}function restoreAssetLiterals(code,blobs){if(!blobs||!blobs.length)return code;return`globalThis.${RAW_ASSET_SLOT}=${JSON.stringify(blobs)};${code}`}async function applyObfuscation(code,level){const n=Number(level)||0;if(n<=0||typeof code!=="string"||!code)return code;let out=code;let assetBlobs=[];const obf=obfuscatorOptionsForCocos(n);if(obf){if(n>=4){console.log("[cocos] level 4 on the main bundle uses base64 string arrays; RC4 and full control-flow do not finish")}const peeled=peelAssetLiterals(out);assetBlobs=peeled.blobs;if(assetBlobs.length){const bytes=assetBlobs.reduce((sum,item)=>sum+item.length,0);console.log(`[cocos] keep ${assetBlobs.length} asset literals out of obfuscation (${bytes} chars)`)}const JavaScriptObfuscator=require("javascript-obfuscator");out=JavaScriptObfuscator.obfuscate(peeled.peeled,{...obf,reservedNames:[...(obf.reservedNames||[]),RAW_ASSET_SLOT],reservedStrings:[...(obf.reservedStrings||[]),RAW_ASSET_SLOT]}).getObfuscatedCode()}if(n>=1){const{minify}=require("terser");const result=await minify(out,terserOptions());if(!result||!result.code){throw new Error(`terser produced empty output at obfuscate level ${n}`)}out=result.code}return restoreAssetLiterals(out,assetBlobs)}module.exports={LEVEL_NAMES,RAW_ASSET_SLOT,obfuscateLevelName,obfuscatorOptionsForLevel,obfuscatorOptionsForCocos,terserOptions,isCocosBusinessScript,isRawAssetLiteral,peelAssetLiterals,applyObfuscation};
@@ -7,4 +7,15 @@ System.register("chunks:///_virtual/Demo.js", ["./rollupPluginModLoBabelHelpers.
7
7
  }
8
8
  };
9
9
  });
10
- `;function loadRegisters(code){const registered=[];const context=vm.createContext({System:{register(name,deps,factory){const ns=factory(function(){});registered.push({name:String(name),deps:Array.from(deps,dep=>String(dep)),keys:ns&&typeof ns==="object"?Object.keys(ns).map(String).sort():[]})}},console});vm.runInContext(code,context,{timeout:5000});return registered}describe("isCocosBusinessScript",()=>{it("matches the main bundle only",()=>{assert.equal(isCocosBusinessScript("assets/main/index.js"),true);assert.equal(isCocosBusinessScript("assets/main/index.abc123.js"),true);assert.equal(isCocosBusinessScript("assets/internal/index.js"),false);assert.equal(isCocosBusinessScript("assets/resources/index.js"),false);assert.equal(isCocosBusinessScript("cocos-js/cc.js"),false)})});describe("applyObfuscation",()=>{it("keeps System.register setters and execute at every level",async()=>{const original=loadRegisters(SAMPLE);assert.deepEqual(original[0].keys,["execute","setters"]);for(const level of[1,2,3,4]){const out=await applyObfuscation(SAMPLE,level);assert.equal(typeof out,"string");assert.ok(out.length>0,`level ${level} empty`);const registered=loadRegisters(out);assert.equal(registered.length,1,`level ${level} register count`);assert.deepEqual(registered[0].keys,["execute","setters"],`level ${level} keys`);assert.deepEqual(registered[0].deps,original[0].deps,`level ${level} deps`)}});it("level 0 and 1 do not enable the obfuscator",()=>{assert.equal(obfuscatorOptionsForLevel(0),null);assert.equal(obfuscatorOptionsForLevel(1),null);assert.equal(obfuscatorOptionsForLevel(2).stringArray,true);assert.equal(obfuscatorOptionsForLevel(3).deadCodeInjection,true);assert.equal(obfuscatorOptionsForLevel(4).stringArrayEncoding[0],"rc4");assert.equal(obfuscatorOptionsForCocos(4).stringArrayEncoding[0],"base64");assert.equal(obfuscatorOptionsForCocos(4).stringArrayThreshold,1);assert.equal(obfuscatorOptionsForCocos(4).splitStrings,false)})});
10
+ `;function loadRegisters(code){const registered=[];const context=vm.createContext({System:{register(name,deps,factory){const ns=factory(function(){});registered.push({name:String(name),deps:Array.from(deps,dep=>String(dep)),keys:ns&&typeof ns==="object"?Object.keys(ns).map(String).sort():[]})}},console});vm.runInContext(code,context,{timeout:5000});return registered}describe("isCocosBusinessScript",()=>{it("matches the main bundle only",()=>{assert.equal(isCocosBusinessScript("assets/main/index.js"),true);assert.equal(isCocosBusinessScript("assets/main/index.abc123.js"),true);assert.equal(isCocosBusinessScript("assets/internal/index.js"),false);assert.equal(isCocosBusinessScript("assets/resources/index.js"),false);assert.equal(isCocosBusinessScript("cocos-js/cc.js"),false)})});describe("applyObfuscation",()=>{it("keeps System.register setters and execute at every level",async()=>{const original=loadRegisters(SAMPLE);assert.deepEqual(original[0].keys,["execute","setters"]);for(const level of[1,2,3,4]){const out=await applyObfuscation(SAMPLE,level);assert.equal(typeof out,"string");assert.ok(out.length>0,`level ${level} empty`);const registered=loadRegisters(out);assert.equal(registered.length,1,`level ${level} register count`);assert.deepEqual(registered[0].keys,["execute","setters"],`level ${level} keys`);assert.deepEqual(registered[0].deps,original[0].deps,`level ${level} deps`)}});it("keeps data urls raw and still obfuscates gameplay strings",async()=>{const blob=`data:image/png;base64,${"A".repeat(800)}`;const code=`
11
+ System.register("chunks:///_virtual/Demo.js", ["cc"], function (exports) {
12
+ return {
13
+ setters: [function () {}],
14
+ execute: function () {
15
+ var secret = "player-token";
16
+ exports("pic", function () { return "${blob}"; });
17
+ exports("secret", function () { return secret; });
18
+ }
19
+ };
20
+ });
21
+ `;const out=await applyObfuscation(code,2);assert.ok(out.includes(blob),"data url should stay plaintext");assert.equal(out.includes("player-token"),false);const exported={};vm.runInContext(out,vm.createContext({globalThis:global,System:{register(_name,_deps,factory){const ns=factory((key,val)=>{exported[key]=val});ns.execute()}}}),{timeout:5000});assert.equal(exported.pic(),blob);assert.equal(exported.secret(),"player-token")});it("level 0 and 1 do not enable the obfuscator",()=>{assert.equal(obfuscatorOptionsForLevel(0),null);assert.equal(obfuscatorOptionsForLevel(1),null);assert.equal(obfuscatorOptionsForLevel(2).stringArray,true);assert.equal(obfuscatorOptionsForLevel(3).deadCodeInjection,true);assert.equal(obfuscatorOptionsForLevel(4).stringArrayEncoding[0],"rc4");assert.equal(obfuscatorOptionsForCocos(4).stringArrayEncoding[0],"base64");assert.equal(obfuscatorOptionsForCocos(4).stringArrayThreshold,1);assert.equal(obfuscatorOptionsForCocos(4).splitStrings,false)})});
@@ -11,7 +11,7 @@
11
11
  *//**
12
12
  * 所有埋点方法配置
13
13
  * @type {TrackingMethod[]}
14
- */const TRACKING_METHODS=[{name:"onPlaycraftTrackingInit",required:true,description:"SDK \u521D\u59CB\u5316\u5F00\u59CB",channels:["Bigabid:mraid_viewable","InMobi:Ad_Load_Start","AppLovin:LOADING"]},{name:"onPlaycraftTrackingLoading",required:true,description:"\u6E38\u620F\u8D44\u6E90\u52A0\u8F7D\u4E2D",channels:["Bigabid:game_viewable","InMobi:Ad_Viewable","AppLovin:LOADED"]},{name:"onPlaycraftTrackingLoaded",required:true,description:"\u4E3B\u573A\u666F\u5C31\u7EEA\uFF0C\u7528\u6237\u53EF\u4EA4\u4E92",channels:["AppLovin:DISPLAYED"]},{name:"onPlaycraftChallengeStart",required:true,description:"\u6311\u6218\u5F00\u59CB\uFF08\u9996\u6B21\u7528\u6237\u4EA4\u4E92\uFF09",channels:["Bigabid:engagement","InMobi:First_Engagement","AppLovin:CHALLENGE_STARTED"]},{name:"onPlaycraftChallengeProgress",required:false,description:"\u8FDB\u5EA6\u767E\u5206\u6BD4\u8FFD\u8E2A\uFF08\u53EF\u9009\uFF09",channels:["AppLovin:CHALLENGE_PASS_*"]},{name:"onPlaycraftChallengeSuccess",required:true,description:"\u6E38\u620F\u6311\u6218\u6210\u529F",channels:["AppLovin:CHALLENGE_SOLVED","Bigabid:complete","InMobi:Gameplay_Complete"]},{name:"onPlaycraftChallengeFinish",required:false,description:"\u6E38\u620F\u7ED3\u675F\uFF08\u53EF\u9009\uFF09",channels:["Bigabid:complete","InMobi:Gameplay_Complete","AppLovin:ENDCARD_SHOWN"]},{name:"onPlaycraftInstall",required:true,description:"\u70B9\u51FB\u5B89\u88C5",channels:["Bigabid:click","InMobi:DSP_Click","AppLovin:CTA_CLICKED"]},{name:"onPlaycraftRetry",required:false,description:"\u91CD\u8BD5\u6E38\u620F\uFF08\u53EF\u9009\uFF09",channels:["AppLovin:CHALLENGE_RETRY"]},{name:"onPlaycraftChallengeFailed",required:true,description:"\u6E38\u620F\u6311\u6218\u5931\u8D25",channels:["AppLovin:CHALLENGE_FAILED"]}];/**
14
+ */const TRACKING_METHODS=[{name:"onPlaycraftTrackingInit",required:true,description:"SDK \u521D\u59CB\u5316\u5F00\u59CB",channels:["Bigabid:mraid_viewable","InMobi:Ad_Load_Start","AppLovin:LOADING"]},{name:"onPlaycraftTrackingLoading",required:true,description:"\u6E38\u620F\u8D44\u6E90\u52A0\u8F7D\u4E2D",channels:["Bigabid:game_viewable","InMobi:Ad_Viewable","AppLovin:LOADED"]},{name:"onPlaycraftTrackingLoaded",required:true,description:"\u4E3B\u573A\u666F\u5C31\u7EEA\uFF0C\u7528\u6237\u53EF\u4EA4\u4E92",channels:["AppLovin:DISPLAYED"]},{name:"onPlaycraftChallengeStart",required:true,description:"\u6311\u6218\u5F00\u59CB\uFF08\u9996\u6B21\u7528\u6237\u4EA4\u4E92\uFF09",channels:["Bigabid:engagement","InMobi:First_Engagement","AppLovin:CHALLENGE_STARTED"]},{name:"onPlaycraftChallengeProgress",required:false,description:"\u8FDB\u5EA6\u767E\u5206\u6BD4\u8FFD\u8E2A\uFF08\u53EF\u9009\uFF09",channels:["AppLovin:CHALLENGE_PASS_*"]},{name:"onPlaycraftChallengeSuccess",required:true,description:"\u6E38\u620F\u6311\u6218\u6210\u529F",channels:["AppLovin:CHALLENGE_SOLVED","Bigabid:complete","InMobi:Gameplay_Complete"]},{name:"onPlaycraftChallengeFinish",required:false,description:"\u6E38\u620F\u7ED3\u675F\uFF08\u53EF\u9009\uFF09",channels:["Bigabid:complete","InMobi:Gameplay_Complete","AppLovin:ENDCARD_SHOWN"]},{name:"onPlaycraftInstall",required:true,description:"\u70B9\u51FB\u5B89\u88C5",channels:["Bigabid:click","InMobi:DSP_Click","AppLovin:CTA_CLICKED"]},{name:"onPlaycraftRetry",required:false,description:"\u91CD\u8BD5\u6E38\u620F\uFF08\u53EF\u9009\uFF09",channels:["AppLovin:CHALLENGE_RETRY"]},{name:"onPlaycraftChallengeFailed",required:false,description:"\u6E38\u620F\u6311\u6218\u5931\u8D25",channels:["AppLovin:CHALLENGE_FAILED"]}];/**
15
15
  * SDK 静态方法映射到 tracking 方法
16
16
  * 这些 SDK 方法内部会调用对应的 tracking 方法
17
17
  */const SDK_TO_TRACKING_MAP={"playcraftInit":"onPlaycraftTrackingInit","playcraftStart":"onPlaycraftTrackingLoading","playcraftInteractive":"onPlaycraftTrackingLoaded","playcraftChallengeStart":"onPlaycraftChallengeStart","recordPlaycraftChallengeProgress":"onPlaycraftChallengeProgress","playcraftRecordSuccess":"onPlaycraftChallengeSuccess","playcraftFinish":"onPlaycraftChallengeFinish","playcraftInstall":"onPlaycraftInstall","playcraftRetry":"onPlaycraftRetry","playcraftRecordFailed":"onPlaycraftChallengeFailed"};/** Phaser / Three 游戏逻辑在 src;Cocos 在 assets/scripts。不要扫 library/temp。 */const ENGINE_SCAN_ROOTS={cocos:["assets/scripts"],phaser:["src"],threejs:["src"]};const DEFAULT_EXCLUDE_DIRS=["node_modules","dist","build",".git",".claude","library","temp","profiles","native","local","dist-all"];function detectProjectEngine(projectRoot,explicit){if(explicit)return String(explicit).toLowerCase();try{const configPath=path.join(projectRoot,"builds.config.js");if(fs.existsSync(configPath)){delete require.cache[require.resolve(configPath)];const cfg=require(configPath);if(cfg&&cfg.engine)return String(cfg.engine).toLowerCase()}}catch{// ignore invalid builds.config.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcraft/devkit",
3
- "version": "1.0.31-beta.15",
3
+ "version": "1.0.31-beta.17",
4
4
  "description": "HTML5 Playable Ads 构建工具,支持多广告渠道打包",
5
5
  "main": "core/index.js",
6
6
  "module": "core/index.js",