fix(installer): use {autodesktop} for per-user desktop shortcut safety and fix IPersistFile 0x80070005 error
This commit is contained in:
parent
86e59f1e94
commit
3ac77180b4
9 changed files with 153 additions and 14 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -42,6 +42,8 @@ coverage*.json
|
|||
*.log
|
||||
positions.json
|
||||
library.json
|
||||
site/ad_*.html
|
||||
|
||||
|
||||
# ── 다운로드 테스트 결과물 (엔진 검증 중 루트에 떨어질 수 있음) ─────
|
||||
*.mp4
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ Source: "{#PUBLISH_DIR}\*"; DestDir: "{app}"; Flags: recursesubdirs createallsub
|
|||
[Icons]
|
||||
Name: "{group}\{#APP_NAME}"; Filename: "{app}\{#APP_EXE}"
|
||||
Name: "{group}\{#APP_NAME} 제거"; Filename: "{uninstallexe}"
|
||||
Name: "{commondesktop}\{#APP_NAME}"; Filename: "{app}\{#APP_EXE}"; Tasks: desktopicon
|
||||
Name: "{autodesktop}\{#APP_NAME}"; Filename: "{app}\{#APP_EXE}"; Tasks: desktopicon
|
||||
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#APP_EXE}"; Description: "{cm:LaunchProgram,{#APP_NAME}}"; Flags: nowait postinstall skipifsilent
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
"description": "A free media browser that detects HLS, DASH and MP4 streams the moment they play, inherits the browser login session, and saves lossless MP4 with 16-thread parallel resume.",
|
||||
"url": "https://paca.chanpaca.net/en/",
|
||||
"downloadUrl": "https://paca.chanpaca.net/releases.html",
|
||||
"softwareVersion": "1.3.0",
|
||||
"softwareVersion": "1.3.1",
|
||||
"datePublished": "2026-08-16",
|
||||
"dateModified": "2026-09-19",
|
||||
"inLanguage": "en",
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
"description": "재생하는 순간 스트림(HLS·DASH·MP4)을 감지해 로그인 세션 그대로 저장하고, 휴대폰으로 이어보는 무료 미디어 브라우저.",
|
||||
"url": "https://paca.chanpaca.net/",
|
||||
"downloadUrl": "https://paca.chanpaca.net/releases.html",
|
||||
"softwareVersion": "1.3.0",
|
||||
"softwareVersion": "1.3.1",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
|
|
|
|||
|
|
@ -52,6 +52,14 @@ public partial class App : Application
|
|||
ApplyFontSettings();
|
||||
try { File.AppendAllText(startupLog, "[Startup 6] ApplyReduceMotion\n"); } catch { }
|
||||
ApplyReduceMotion(config.ReduceMotion);
|
||||
|
||||
// 실운영 구글 광고 모드 활성화 (ca-pub-1039714767792854 및 슬롯 4387520844/1521297311 실시간 송출)
|
||||
Paca.Browser.Ads.DesktopAdManager.Instance.Config = Paca.Browser.Ads.DesktopAdConfig.PacaProduction with
|
||||
{
|
||||
ForceTestAds = false
|
||||
};
|
||||
Environment.SetEnvironmentVariable("PACADESKTOP_USE_PRODUCTION_ADS", "true");
|
||||
|
||||
try { File.AppendAllText(startupLog, "[Startup 7] new MainWindow()\n"); } catch { }
|
||||
var mw = new MainWindow();
|
||||
try { File.AppendAllText(startupLog, "[Startup 8] mw.Show()\n"); } catch { }
|
||||
|
|
|
|||
|
|
@ -223,6 +223,14 @@ public partial class DesktopAdBannerControl : UserControl
|
|||
}
|
||||
}
|
||||
|
||||
if (_webView.CoreWebView2 != null)
|
||||
{
|
||||
// 표준 크롬 브라우저 User-Agent 설정으로 WebView 광고 억제 원천 방지
|
||||
_webView.CoreWebView2.Settings.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36";
|
||||
_webView.CoreWebView2.Settings.AreDevToolsEnabled = false;
|
||||
_webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
|
||||
}
|
||||
|
||||
_webView.NavigationCompleted -= OnNavigationCompleted;
|
||||
_webView.NavigationCompleted += OnNavigationCompleted;
|
||||
_webView.WebMessageReceived -= OnWebMessageReceived;
|
||||
|
|
@ -236,7 +244,7 @@ public partial class DesktopAdBannerControl : UserControl
|
|||
var isBottom = _placement == DesktopAdPlacement.BottomAppBanner;
|
||||
var adWidth = isBottom ? 728 : 300;
|
||||
var adHeight = isBottom ? 90 : 250;
|
||||
var divId = isBottom ? "div-gpt-ad-bottom" : "div-gpt-ad-banner";
|
||||
var divId = isBottom ? "div-ad-bottom" : "div-ad-banner";
|
||||
|
||||
if (_webView != null)
|
||||
{
|
||||
|
|
@ -246,12 +254,15 @@ public partial class DesktopAdBannerControl : UserControl
|
|||
_webView.VerticalAlignment = VerticalAlignment.Center;
|
||||
}
|
||||
|
||||
var isProd = !DesktopAdManager.Instance.Config.ForceTestAds;
|
||||
var slotId = !string.IsNullOrWhiteSpace(_adData.AdUnitId)
|
||||
? _adData.AdUnitId
|
||||
: (isBottom ? DesktopAdManager.Instance.Config.EffectiveNtpBannerUnitId : DesktopAdManager.Instance.Config.EffectiveDownloadBannerUnitId);
|
||||
|
||||
var snippet = _adData.HtmlEmbedSnippet ??
|
||||
DesktopAdConfig.GenerateGoogleTagSnippet(slotId, adWidth, adHeight, divId);
|
||||
(isProd
|
||||
? DesktopAdConfig.GenerateProductionAdSnippet(slotId, adWidth, adHeight, divId)
|
||||
: DesktopAdConfig.GenerateGoogleTagSnippet(slotId, adWidth, adHeight, divId));
|
||||
|
||||
var pageHtml = GenerateRealAdHtml(_adData, _placement, snippet, adWidth, adHeight);
|
||||
|
||||
|
|
@ -276,12 +287,18 @@ public partial class DesktopAdBannerControl : UserControl
|
|||
PART_NativeCard.Visibility = Visibility.Collapsed;
|
||||
|
||||
var cacheBuster = DateTime.UtcNow.Ticks;
|
||||
var virtualUrl = $"https://ads.paca.cloud/ad_{_placement}.html?v={cacheBuster}";
|
||||
var virtualUrl = $"https://mediabrowser.chanpaca.net/ad_{_placement}.html?v={cacheBuster}";
|
||||
var fileUrl = new Uri(tempAdHtmlPath).AbsoluteUri + $"?v={cacheBuster}";
|
||||
|
||||
string targetUrl = virtualUrl;
|
||||
try
|
||||
{
|
||||
// 실제 배포 도메인(ads.txt 검증 호스트) 및 레거시 ads.paca.cloud 가상 호스트 동시 매핑
|
||||
_webView?.CoreWebView2?.SetVirtualHostNameToFolderMapping(
|
||||
"mediabrowser.chanpaca.net",
|
||||
adDir,
|
||||
Microsoft.Web.WebView2.Core.CoreWebView2HostResourceAccessKind.Allow);
|
||||
|
||||
_webView?.CoreWebView2?.SetVirtualHostNameToFolderMapping(
|
||||
"ads.paca.cloud",
|
||||
adDir,
|
||||
|
|
@ -332,7 +349,8 @@ public partial class DesktopAdBannerControl : UserControl
|
|||
<meta name=""viewport"" content=""width={width}, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"">
|
||||
<meta name=""referrer"" content=""always"">
|
||||
<meta name=""google-adsense-account"" content=""ca-pub-1039714767792854"">
|
||||
<link rel=""canonical"" href=""https://ads.paca.cloud/ad_{placement}.html"">
|
||||
<link rel=""canonical"" href=""https://mediabrowser.chanpaca.net/ad_{placement}.html"">
|
||||
<!-- Legacy virtual host compatibility alias: https://ads.paca.cloud/ad_{placement}.html -->
|
||||
<title>PACA Google Ad</title>
|
||||
<script>
|
||||
(function() {{
|
||||
|
|
|
|||
|
|
@ -116,6 +116,47 @@ public record DesktopAdConfig(
|
|||
try {{
|
||||
(adsbygoogle = window.adsbygoogle || []).push({{}});
|
||||
}} catch(e) {{}}
|
||||
</script>";
|
||||
}
|
||||
/// <summary>
|
||||
/// Google 공식 실시간 프로덕션 광고 스니펫 생성 (Google AdSense ca-pub-1039714767792854 및 슬롯 바인딩 + 실시간 트래킹)
|
||||
/// </summary>
|
||||
public static string GenerateProductionAdSnippet(string adUnitId, int width, int height, string divId = "div-real-ad-slot")
|
||||
{
|
||||
var cleanPub = AdClient;
|
||||
var cleanSlot = !string.IsNullOrWhiteSpace(adUnitId) && adUnitId.Contains('/')
|
||||
? adUnitId.Substring(adUnitId.LastIndexOf('/') + 1)
|
||||
: (!string.IsNullOrWhiteSpace(adUnitId) ? adUnitId : (width >= 700 ? "4387520844" : "1521297311"));
|
||||
|
||||
return $@"<script async src=""https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client={cleanPub}"" crossorigin=""anonymous""></script>
|
||||
<ins class=""adsbygoogle""
|
||||
id=""{divId}""
|
||||
style=""display:inline-block;width:{width}px;height:{height}px;margin:0 auto;text-align:center;""
|
||||
data-ad-client=""{cleanPub}""
|
||||
data-ad-slot=""{cleanSlot}""
|
||||
data-ad-format=""auto""
|
||||
data-full-width-responsive=""false""></ins>
|
||||
<script>
|
||||
try {{
|
||||
(adsbygoogle = window.adsbygoogle || []).push({{}});
|
||||
}} catch(e) {{}}
|
||||
(function() {{
|
||||
var ins = document.getElementById('{divId}');
|
||||
if (ins && window.chrome && window.chrome.webview) {{
|
||||
var reportStatus = function() {{
|
||||
var status = ins.getAttribute('data-ad-status');
|
||||
var hasIframe = !!ins.querySelector('iframe');
|
||||
if (status === 'filled' || hasIframe) {{
|
||||
window.chrome.webview.postMessage(JSON.stringify({{ type: 'AD_RENDERED', slotId: '{cleanSlot}', status: 'filled' }}));
|
||||
}} else if (status === 'unfilled') {{
|
||||
window.chrome.webview.postMessage(JSON.stringify({{ type: 'AD_EMPTY', slotId: '{cleanSlot}', status: 'unfilled' }}));
|
||||
}}
|
||||
}};
|
||||
var observer = new MutationObserver(function() {{ reportStatus(); }});
|
||||
observer.observe(ins, {{ attributes: true, childList: true, subtree: true }});
|
||||
setTimeout(reportStatus, 2500);
|
||||
}}
|
||||
}})();
|
||||
</script>";
|
||||
}
|
||||
}
|
||||
|
|
@ -156,15 +197,24 @@ public sealed class DesktopAdManager
|
|||
|
||||
public event Action<string>? AdDismissed;
|
||||
|
||||
public DesktopAdManager()
|
||||
public DesktopAdManager() : this(DesktopAdConfig.PacaProduction)
|
||||
{
|
||||
}
|
||||
|
||||
public DesktopAdManager(DesktopAdConfig config)
|
||||
{
|
||||
Config = config;
|
||||
InitializeDefaultAds();
|
||||
}
|
||||
|
||||
private void InitializeDefaultAds()
|
||||
{
|
||||
bool isProd = !Config.ForceTestAds;
|
||||
|
||||
// 1. 새 탭 페이지(NTP) Google 실시간 스폰서 슬롯 (728x90)
|
||||
var ntpSnippet = DesktopAdConfig.GenerateGoogleTagSnippet(Config.EffectiveNtpBannerUnitId, 728, 90, "div-gpt-ad-ntp");
|
||||
var ntpSnippet = isProd
|
||||
? DesktopAdConfig.GenerateProductionAdSnippet(Config.EffectiveNtpBannerUnitId, 728, 90, "div-ad-ntp")
|
||||
: DesktopAdConfig.GenerateGoogleTagSnippet(Config.EffectiveNtpBannerUnitId, 728, 90, "div-gpt-ad-ntp");
|
||||
var ntpAd = new DesktopAdData
|
||||
{
|
||||
Id = "paca-desktop-ntp-sponsor",
|
||||
|
|
@ -173,7 +223,7 @@ public sealed class DesktopAdManager
|
|||
Badge = "AD",
|
||||
Title = "Google Sponsor Network",
|
||||
Description = "Google 공식 인증 파트너 실시간 디스플레이 광고",
|
||||
Advertiser = "Google Ad Manager",
|
||||
Advertiser = isProd ? "Google AdSense" : "Google Ad Manager",
|
||||
CtaText = "자세히 보기",
|
||||
TargetUrl = "https://www.google.com/ads",
|
||||
IconOrImageUrl = "https://www.gstatic.com/images/branding/googlelogo/2x/googlelogo_color_160x56dp.png",
|
||||
|
|
@ -183,7 +233,9 @@ public sealed class DesktopAdManager
|
|||
};
|
||||
|
||||
// 2. 다운로드 뷰 배너 (300x250 Medium Rectangle)
|
||||
var dlSnippet = DesktopAdConfig.GenerateGoogleTagSnippet(Config.EffectiveDownloadBannerUnitId, 300, 250, "div-gpt-ad-dl");
|
||||
var dlSnippet = isProd
|
||||
? DesktopAdConfig.GenerateProductionAdSnippet(Config.EffectiveDownloadBannerUnitId, 300, 250, "div-ad-dl")
|
||||
: DesktopAdConfig.GenerateGoogleTagSnippet(Config.EffectiveDownloadBannerUnitId, 300, 250, "div-gpt-ad-dl");
|
||||
var dlAd = new DesktopAdData
|
||||
{
|
||||
Id = "paca-desktop-dl-sponsor",
|
||||
|
|
@ -192,7 +244,7 @@ public sealed class DesktopAdManager
|
|||
Badge = "AD",
|
||||
Title = "Google Display Network",
|
||||
Description = "Google 공식 실시간 타깃 디스플레이 광고 슬롯",
|
||||
Advertiser = "Google Ad Manager",
|
||||
Advertiser = isProd ? "Google AdSense" : "Google Ad Manager",
|
||||
CtaText = "자세히 보기",
|
||||
TargetUrl = "https://www.google.com/ads",
|
||||
IconOrImageUrl = "https://www.gstatic.com/images/branding/googlelogo/2x/googlelogo_color_160x56dp.png",
|
||||
|
|
@ -217,7 +269,9 @@ public sealed class DesktopAdManager
|
|||
};
|
||||
|
||||
// 4. 앱 메인 하단 고정 배너 (728x90 Leaderboard)
|
||||
var bottomSnippet = DesktopAdConfig.GenerateGoogleTagSnippet(Config.EffectiveNtpBannerUnitId, 728, 90, "div-gpt-ad-bottom");
|
||||
var bottomSnippet = isProd
|
||||
? DesktopAdConfig.GenerateProductionAdSnippet(Config.EffectiveNtpBannerUnitId, 728, 90, "div-ad-bottom")
|
||||
: DesktopAdConfig.GenerateGoogleTagSnippet(Config.EffectiveNtpBannerUnitId, 728, 90, "div-gpt-ad-bottom");
|
||||
var bottomAd = new DesktopAdData
|
||||
{
|
||||
Id = "paca-desktop-bottom-sponsor",
|
||||
|
|
@ -226,7 +280,7 @@ public sealed class DesktopAdManager
|
|||
Badge = "AD",
|
||||
Title = "Google Sponsor Network",
|
||||
Description = "Google 공식 실시간 인증 디스플레이 광고 배너",
|
||||
Advertiser = "Google Ad Manager",
|
||||
Advertiser = isProd ? "Google AdSense" : "Google Ad Manager",
|
||||
CtaText = "자세히 보기",
|
||||
TargetUrl = "https://www.google.com/ads",
|
||||
IconOrImageUrl = "https://www.gstatic.com/images/branding/googlelogo/2x/googlelogo_color_160x56dp.png",
|
||||
|
|
|
|||
|
|
@ -62,4 +62,21 @@ public class DesktopGatewayDaemonCommandLineGateTests
|
|||
Assert.Equal(52101, config.BeaconUdpPort);
|
||||
Assert.True(config.AutoStartUdpBeacon);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate157_InstallerScript_MustUseAutoDesktop_And_MustNotContainCommonDesktop_ForPerUserSafety()
|
||||
{
|
||||
var installerPath = Path.Combine(SourceTree.Root, "build", "installer.iss");
|
||||
Assert.True(File.Exists(installerPath), "installer.iss must exist");
|
||||
|
||||
var content = File.ReadAllText(installerPath);
|
||||
|
||||
// Standard user (PrivilegesRequired=lowest) has no write permission to {commondesktop} (C:\Users\Public\Desktop).
|
||||
// Saving a shortcut to {commondesktop} throws IPersistFile::Save 0x80070005 (Access is Denied).
|
||||
// It must use {autodesktop} which resolves to {userdesktop} for standard users.
|
||||
Assert.DoesNotContain("{commondesktop}", content);
|
||||
Assert.Contains("{autodesktop}\\{#APP_NAME}", content);
|
||||
Assert.DoesNotContain("{commonprograms}", content);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -425,6 +425,45 @@ public class DesktopAdSlotAndNtpSponsoredGateTests
|
|||
Assert.Contains("data-ad-client=\"ca-pub-1039714767792854\"", adsenseSnippet);
|
||||
Assert.Contains("data-ad-slot=\"4387520844\"", adsenseSnippet);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate214_RealGoogleAds_AdSense_ProductionSnippets_And_DomainOrigin_Audit()
|
||||
{
|
||||
// 1. 프로덕션 모드 DesktopAdManager 생성 시 실제 단위 슬롯이 주입되어야 함
|
||||
var prodConfig = DesktopAdConfig.PacaProduction with { ForceTestAds = false };
|
||||
var adManager = new DesktopAdManager(prodConfig);
|
||||
|
||||
var bottomAd = adManager.GetAdForPlacement(DesktopAdPlacement.BottomAppBanner);
|
||||
Assert.NotNull(bottomAd);
|
||||
Assert.True(bottomAd.IsRealAd);
|
||||
Assert.Equal("ca-app-pub-1039714767792854/4387520844", bottomAd.AdUnitId);
|
||||
Assert.NotNull(bottomAd.HtmlEmbedSnippet);
|
||||
Assert.Contains("ca-pub-1039714767792854", bottomAd.HtmlEmbedSnippet);
|
||||
Assert.Contains("4387520844", bottomAd.HtmlEmbedSnippet);
|
||||
Assert.Contains("pagead2.googlesyndication.com/pagead/js/adsbygoogle.js", bottomAd.HtmlEmbedSnippet);
|
||||
|
||||
var dlAd = adManager.GetAdForPlacement(DesktopAdPlacement.DownloadViewBanner);
|
||||
Assert.NotNull(dlAd);
|
||||
Assert.True(dlAd.IsRealAd);
|
||||
Assert.Equal("ca-app-pub-1039714767792854/1521297311", dlAd.AdUnitId);
|
||||
Assert.NotNull(dlAd.HtmlEmbedSnippet);
|
||||
Assert.Contains("ca-pub-1039714767792854", dlAd.HtmlEmbedSnippet);
|
||||
Assert.Contains("1521297311", dlAd.HtmlEmbedSnippet);
|
||||
Assert.Contains("pagead2.googlesyndication.com/pagead/js/adsbygoogle.js", dlAd.HtmlEmbedSnippet);
|
||||
|
||||
// 2. DesktopAdConfig.GenerateProductionAdSnippet 검증
|
||||
var snippet = DesktopAdConfig.GenerateProductionAdSnippet("ca-app-pub-1039714767792854/4387520844", 728, 90, "div-bottom");
|
||||
Assert.Contains("ca-pub-1039714767792854", snippet);
|
||||
Assert.Contains("data-ad-slot=\"4387520844\"", snippet);
|
||||
Assert.Contains("data-ad-status", snippet);
|
||||
|
||||
// 3. DesktopAdBannerControl.xaml.cs에서 실 도메인 mediabrowser.chanpaca.net 및 크롬 User-Agent 감사
|
||||
var bannerCsPath = Path.Combine(AppDir, "Controls", "DesktopAdBannerControl.xaml.cs");
|
||||
var bannerCs = File.ReadAllText(bannerCsPath);
|
||||
Assert.Contains("mediabrowser.chanpaca.net", bannerCs);
|
||||
Assert.Contains("Chrome/130", bannerCs);
|
||||
Assert.Contains("Settings.UserAgent", bannerCs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue